kctl-litellm 0.2.0__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.
- kctl_litellm-0.2.0/.gitignore +33 -0
- kctl_litellm-0.2.0/PKG-INFO +16 -0
- kctl_litellm-0.2.0/README.md +78 -0
- kctl_litellm-0.2.0/pyproject.toml +42 -0
- kctl_litellm-0.2.0/skills/litellm-admin/SKILL.md +149 -0
- kctl_litellm-0.2.0/src/kctl_litellm/__init__.py +3 -0
- kctl_litellm-0.2.0/src/kctl_litellm/__main__.py +5 -0
- kctl_litellm-0.2.0/src/kctl_litellm/cli.py +126 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/__init__.py +1 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/budgets_cmd.py +116 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/config_cmd.py +225 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/health_cmd.py +135 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/keys_cmd.py +234 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/logs_cmd.py +104 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/models_cmd.py +131 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/spend_cmd.py +122 -0
- kctl_litellm-0.2.0/src/kctl_litellm/commands/teams_cmd.py +161 -0
- kctl_litellm-0.2.0/src/kctl_litellm/core/__init__.py +1 -0
- kctl_litellm-0.2.0/src/kctl_litellm/core/callbacks.py +26 -0
- kctl_litellm-0.2.0/src/kctl_litellm/core/client.py +244 -0
- kctl_litellm-0.2.0/src/kctl_litellm/core/config.py +156 -0
- kctl_litellm-0.2.0/src/kctl_litellm/core/exceptions.py +51 -0
- kctl_litellm-0.2.0/tests/__init__.py +0 -0
- kctl_litellm-0.2.0/tests/conftest.py +58 -0
- kctl_litellm-0.2.0/tests/test_cli.py +30 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.idea/
|
|
16
|
+
.vscode/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Environment
|
|
32
|
+
.env
|
|
33
|
+
.env.local
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kctl-litellm
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Kodemeio LiteLLM CLI — manage LiteLLM proxy instances
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: httpx>=0.27.0
|
|
7
|
+
Requires-Dist: kctl-lib>=0.4.0
|
|
8
|
+
Requires-Dist: pydantic>=2.10.0
|
|
9
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
10
|
+
Requires-Dist: rich>=13.9.0
|
|
11
|
+
Requires-Dist: typer>=0.15.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: mypy>=1.14.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# kctl-litellm
|
|
2
|
+
|
|
3
|
+
Kodemeio CLI for managing [LiteLLM](https://github.com/BerriAI/litellm) proxy instances. Part of the [kodemeio-platform](https://github.com/tgunawandev/kodemeio-platform) monorepo.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv pip install -e packages/kctl-litellm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Add a profile to `~/.config/kodemeio/config.yaml`:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
profiles:
|
|
17
|
+
production:
|
|
18
|
+
litellm:
|
|
19
|
+
url: https://llm.terakidz.com
|
|
20
|
+
master_key: ${KCTL_LITELLM_MASTER_KEY}
|
|
21
|
+
container_name: litellm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or use the CLI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
kctl-litellm config add production --url https://llm.terakidz.com --master-key sk-...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Health
|
|
34
|
+
kctl-litellm health check # Full health check with model status
|
|
35
|
+
kctl-litellm health ping # Quick connectivity check
|
|
36
|
+
kctl-litellm health liveliness # Liveness probe (no auth)
|
|
37
|
+
|
|
38
|
+
# Models
|
|
39
|
+
kctl-litellm models list # List available models
|
|
40
|
+
kctl-litellm models info # Detailed model info with pricing
|
|
41
|
+
|
|
42
|
+
# Key Management
|
|
43
|
+
kctl-litellm keys generate --key-alias my-app --max-budget 100
|
|
44
|
+
kctl-litellm keys list
|
|
45
|
+
kctl-litellm keys info <token>
|
|
46
|
+
kctl-litellm keys delete <token>
|
|
47
|
+
|
|
48
|
+
# Teams & Budgets
|
|
49
|
+
kctl-litellm teams create --team-alias engineering --max-budget 500
|
|
50
|
+
kctl-litellm teams list
|
|
51
|
+
kctl-litellm budgets create --budget-id dev-tier --max-budget 200
|
|
52
|
+
kctl-litellm budgets list
|
|
53
|
+
|
|
54
|
+
# Spend & Logs
|
|
55
|
+
kctl-litellm spend summary # Total spend by model
|
|
56
|
+
kctl-litellm spend daily # Daily activity (last 7 days)
|
|
57
|
+
kctl-litellm logs list --limit 50 # Recent request logs
|
|
58
|
+
|
|
59
|
+
# Config
|
|
60
|
+
kctl-litellm config profiles # List profiles
|
|
61
|
+
kctl-litellm config show # Show current config (secrets masked)
|
|
62
|
+
kctl-litellm config use staging # Switch default profile
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Global Options
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
--json Output as JSON
|
|
69
|
+
--quiet, -q Suppress info messages
|
|
70
|
+
--profile, -p Config profile name
|
|
71
|
+
--url LiteLLM URL override
|
|
72
|
+
--version, -V Show version
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Dependencies
|
|
76
|
+
|
|
77
|
+
- Python >= 3.12
|
|
78
|
+
- [kctl-lib](../kctl-lib) >= 0.4.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kctl-litellm"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Kodemeio LiteLLM CLI — manage LiteLLM proxy instances"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"kctl-lib>=0.4.0",
|
|
12
|
+
"typer>=0.15.0",
|
|
13
|
+
"rich>=13.9.0",
|
|
14
|
+
"pydantic>=2.10.0",
|
|
15
|
+
"pyyaml>=6.0.2",
|
|
16
|
+
"httpx>=0.27.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=8.3.0",
|
|
22
|
+
"ruff>=0.9.0",
|
|
23
|
+
"mypy>=1.14.0",
|
|
24
|
+
"types-PyYAML>=6.0.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
kctl-litellm = "kctl_litellm.cli:_run"
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources]
|
|
31
|
+
kctl-lib = { workspace = true }
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/kctl_litellm"]
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
target-version = "py312"
|
|
38
|
+
line-length = 120
|
|
39
|
+
|
|
40
|
+
[tool.mypy]
|
|
41
|
+
python_version = "3.12"
|
|
42
|
+
strict = true
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: litellm-admin
|
|
3
|
+
description: >
|
|
4
|
+
LiteLLM proxy administration via kctl-litellm CLI (7 groups, ~20 commands).
|
|
5
|
+
MUST use for ANY kctl-litellm operation.
|
|
6
|
+
Triggers on: "budget", "config", "health", "kctl-litellm", "keys", "litellm", "llm",
|
|
7
|
+
"logs", "models", "profile", "proxy", "spend", "teams", "tokens", "virtual-key".
|
|
8
|
+
Auto-generated: 2026-05-03
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# litellm-admin -- kctl-litellm CLI Reference
|
|
12
|
+
|
|
13
|
+
> Auto-generated from `kctl-litellm` command registry. Do not edit manually.
|
|
14
|
+
> To regenerate: `kctl-litellm skill generate`
|
|
15
|
+
> To add custom content: edit `SKILL.extra.md` in the same directory.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
**CLI:** `kctl-litellm`
|
|
20
|
+
**Command groups:** 7
|
|
21
|
+
**Total commands:** ~20
|
|
22
|
+
**Install:** `cd packages/kctl-litellm && uv tool install --editable .`
|
|
23
|
+
|
|
24
|
+
## Global Options
|
|
25
|
+
|
|
26
|
+
| Flag | Description |
|
|
27
|
+
|------|-------------|
|
|
28
|
+
| `--json` | Output as JSON |
|
|
29
|
+
| `--quiet`, `-q` | Suppress info messages |
|
|
30
|
+
| `--profile`, `-p` | Config profile name |
|
|
31
|
+
| `--url` | LiteLLM URL override |
|
|
32
|
+
| `--version`, `-V` | Show version |
|
|
33
|
+
|
|
34
|
+
## Command Reference
|
|
35
|
+
|
|
36
|
+
### `kctl-litellm health` (Services)
|
|
37
|
+
|
|
38
|
+
Check LiteLLM proxy service health.
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
|---------|-------------|
|
|
42
|
+
| `health check` | Full health check via /health endpoint. Shows healthy and unhealthy models. |
|
|
43
|
+
| `health ping` | Quick connectivity check (GET /, status code and response time). |
|
|
44
|
+
| `health liveliness` | Quick liveness check via /health/liveliness (no auth required). |
|
|
45
|
+
|
|
46
|
+
### `kctl-litellm models` (Services)
|
|
47
|
+
|
|
48
|
+
Manage and inspect LiteLLM models.
|
|
49
|
+
|
|
50
|
+
| Command | Description |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| `models list` | List available models via /v1/models. |
|
|
53
|
+
| `models info [<model>]` | Show detailed model info with pricing via /model/info. |
|
|
54
|
+
|
|
55
|
+
### `kctl-litellm keys` (Key Management)
|
|
56
|
+
|
|
57
|
+
Manage LiteLLM virtual keys.
|
|
58
|
+
|
|
59
|
+
| Command | Description |
|
|
60
|
+
|---------|-------------|
|
|
61
|
+
| `keys generate [--team-id] [--max-budget] [--duration] [--models] [--key-alias]` | Generate a new virtual key. |
|
|
62
|
+
| `keys list` | List all virtual keys. |
|
|
63
|
+
| `keys info <key>` | Show detailed key information (key value masked). |
|
|
64
|
+
| `keys update <key> [--max-budget] [--models] [--key-alias] [--duration]` | Update a virtual key's settings. |
|
|
65
|
+
| `keys delete <key> [--force]` | Delete a virtual key. |
|
|
66
|
+
|
|
67
|
+
### `kctl-litellm teams` (Teams & Budgets)
|
|
68
|
+
|
|
69
|
+
Manage LiteLLM teams.
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `teams create --team-alias <alias> [--max-budget] [--models]` | Create a new team. |
|
|
74
|
+
| `teams list` | List all teams. |
|
|
75
|
+
| `teams update <team_id> [--team-alias] [--max-budget] [--models]` | Update a team's settings. |
|
|
76
|
+
| `teams delete <team_id> [--force]` | Delete a team. |
|
|
77
|
+
|
|
78
|
+
### `kctl-litellm budgets` (Teams & Budgets)
|
|
79
|
+
|
|
80
|
+
Manage LiteLLM budgets.
|
|
81
|
+
|
|
82
|
+
| Command | Description |
|
|
83
|
+
|---------|-------------|
|
|
84
|
+
| `budgets create [--budget-id] [--max-budget] [--soft-budget] [--tpm-limit] [--rpm-limit]` | Create a new budget with rate limits. |
|
|
85
|
+
| `budgets list` | List all budgets. |
|
|
86
|
+
|
|
87
|
+
### `kctl-litellm spend` (Usage & Spend)
|
|
88
|
+
|
|
89
|
+
Track LiteLLM spend and usage.
|
|
90
|
+
|
|
91
|
+
| Command | Description |
|
|
92
|
+
|---------|-------------|
|
|
93
|
+
| `spend summary` | Show total spend summary aggregated by model. |
|
|
94
|
+
| `spend daily [--start-date] [--end-date]` | Show daily activity (requests, spend, tokens). |
|
|
95
|
+
|
|
96
|
+
### `kctl-litellm logs` (Usage & Spend)
|
|
97
|
+
|
|
98
|
+
View LiteLLM request spend logs.
|
|
99
|
+
|
|
100
|
+
| Command | Description |
|
|
101
|
+
|---------|-------------|
|
|
102
|
+
| `logs list [--limit] [--start-date] [--end-date] [--api-key]` | List spend logs from /global/spend/logs. |
|
|
103
|
+
|
|
104
|
+
### `kctl-litellm config` (Admin & Config)
|
|
105
|
+
|
|
106
|
+
Manage CLI configuration and profiles.
|
|
107
|
+
|
|
108
|
+
| Command | Description |
|
|
109
|
+
|---------|-------------|
|
|
110
|
+
| `config init` | Initialize CLI configuration. |
|
|
111
|
+
| `config show` | Show configuration (keys masked). |
|
|
112
|
+
| `config add` | Add a new profile. |
|
|
113
|
+
| `config use` | Switch default profile. |
|
|
114
|
+
| `config remove` | Remove a profile. |
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
Shared config: `~/.config/kodemeio/config.yaml`
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
kctl-litellm config init # Interactive setup
|
|
122
|
+
kctl-litellm config show # Show current config
|
|
123
|
+
kctl-litellm self-update # Check for updates
|
|
124
|
+
kctl-litellm completions zsh --install # Install shell completions
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Examples:**
|
|
128
|
+
```bash
|
|
129
|
+
# Check proxy health
|
|
130
|
+
kctl-litellm health check -p kodemeio
|
|
131
|
+
|
|
132
|
+
# List all available models with pricing
|
|
133
|
+
kctl-litellm models info -p kodemeio
|
|
134
|
+
|
|
135
|
+
# Generate a virtual key for an app
|
|
136
|
+
kctl-litellm keys generate --key-alias my-app --max-budget 100 --duration 30d -p kodemeio
|
|
137
|
+
|
|
138
|
+
# Create a team with a budget
|
|
139
|
+
kctl-litellm teams create --team-alias engineering --max-budget 500 -p kodemeio
|
|
140
|
+
|
|
141
|
+
# View spend breakdown by model
|
|
142
|
+
kctl-litellm spend summary -p kodemeio
|
|
143
|
+
|
|
144
|
+
# View daily activity for the last 7 days
|
|
145
|
+
kctl-litellm spend daily -p kodemeio
|
|
146
|
+
|
|
147
|
+
# Browse recent request logs
|
|
148
|
+
kctl-litellm logs list --limit 50 -p kodemeio
|
|
149
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Main CLI entry point for kctl-litellm."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from kctl_lib import handle_cli_error
|
|
9
|
+
|
|
10
|
+
from kctl_litellm import __version__
|
|
11
|
+
from kctl_litellm.commands.budgets_cmd import app as budgets_app
|
|
12
|
+
from kctl_litellm.commands.config_cmd import app as config_app
|
|
13
|
+
from kctl_litellm.commands.health_cmd import app as health_app
|
|
14
|
+
from kctl_litellm.commands.keys_cmd import app as keys_app
|
|
15
|
+
from kctl_litellm.commands.logs_cmd import app as logs_app
|
|
16
|
+
from kctl_litellm.commands.models_cmd import app as models_app
|
|
17
|
+
from kctl_litellm.commands.spend_cmd import app as spend_app
|
|
18
|
+
from kctl_litellm.commands.teams_cmd import app as teams_app
|
|
19
|
+
from kctl_litellm.core.callbacks import AppContext
|
|
20
|
+
from kctl_litellm.core.exceptions import LiteLLMError
|
|
21
|
+
from kctl_lib.self_update import notify_if_outdated
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def version_callback(value: bool) -> None:
|
|
25
|
+
if value:
|
|
26
|
+
typer.echo(f"kctl-litellm {__version__}")
|
|
27
|
+
raise typer.Exit()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app = typer.Typer(
|
|
31
|
+
name="kctl-litellm",
|
|
32
|
+
help="Kodemeio LiteLLM CLI - manage LiteLLM proxy instances.",
|
|
33
|
+
no_args_is_help=True,
|
|
34
|
+
rich_markup_mode="rich",
|
|
35
|
+
pretty_exceptions_enable=False,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.callback()
|
|
40
|
+
def main(
|
|
41
|
+
ctx: typer.Context,
|
|
42
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
43
|
+
quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
|
|
44
|
+
profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
|
|
45
|
+
url: Annotated[str | None, typer.Option("--url", help="LiteLLM URL override")] = None,
|
|
46
|
+
version: Annotated[
|
|
47
|
+
bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
|
|
48
|
+
] = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Kodemeio LiteLLM CLI."""
|
|
51
|
+
ctx.ensure_object(dict)
|
|
52
|
+
ctx.obj = AppContext(
|
|
53
|
+
json_mode=json_output,
|
|
54
|
+
quiet=quiet,
|
|
55
|
+
profile=profile,
|
|
56
|
+
url_override=url,
|
|
57
|
+
)
|
|
58
|
+
notify_if_outdated(ctx.obj.output, "kctl-litellm", __version__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_P_ADMIN = "Admin & Config"
|
|
62
|
+
app.add_typer(config_app, name="config", rich_help_panel=_P_ADMIN)
|
|
63
|
+
|
|
64
|
+
_P_SERVICES = "Services"
|
|
65
|
+
app.add_typer(health_app, name="health", rich_help_panel=_P_SERVICES)
|
|
66
|
+
app.add_typer(models_app, name="models", rich_help_panel=_P_SERVICES)
|
|
67
|
+
|
|
68
|
+
_P_KEYS = "Key Management"
|
|
69
|
+
app.add_typer(keys_app, name="keys", rich_help_panel=_P_KEYS)
|
|
70
|
+
|
|
71
|
+
_P_TEAMS = "Teams & Budgets"
|
|
72
|
+
app.add_typer(teams_app, name="teams", rich_help_panel=_P_TEAMS)
|
|
73
|
+
app.add_typer(budgets_app, name="budgets", rich_help_panel=_P_TEAMS)
|
|
74
|
+
|
|
75
|
+
_P_USAGE = "Usage & Spend"
|
|
76
|
+
app.add_typer(spend_app, name="spend", rich_help_panel=_P_USAGE)
|
|
77
|
+
app.add_typer(logs_app, name="logs", rich_help_panel=_P_USAGE)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command("self-update")
|
|
81
|
+
def self_update_cmd(ctx: typer.Context) -> None:
|
|
82
|
+
"""Check for updates and upgrade kctl-litellm."""
|
|
83
|
+
actx = ctx.obj
|
|
84
|
+
out = actx.output
|
|
85
|
+
from kctl_lib.self_update import check_update
|
|
86
|
+
from kctl_lib.self_update import update as do_update
|
|
87
|
+
|
|
88
|
+
latest = check_update("kctl-litellm", __version__)
|
|
89
|
+
if latest:
|
|
90
|
+
out.info(f"Updating to {latest}...")
|
|
91
|
+
do_update("kctl-litellm")
|
|
92
|
+
out.success(f"Updated to {latest}")
|
|
93
|
+
else:
|
|
94
|
+
out.success("Already up to date")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command()
|
|
98
|
+
def completions(
|
|
99
|
+
shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
|
|
100
|
+
install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Generate or install shell completions."""
|
|
103
|
+
from kctl_lib.completions import get_completion_script, install_completions
|
|
104
|
+
|
|
105
|
+
if install:
|
|
106
|
+
path = install_completions("kctl-litellm", shell)
|
|
107
|
+
if path:
|
|
108
|
+
typer.echo(f"Completions installed to {path}")
|
|
109
|
+
else:
|
|
110
|
+
typer.echo(f"Could not install completions for {shell}", err=True)
|
|
111
|
+
raise typer.Exit(code=1)
|
|
112
|
+
else:
|
|
113
|
+
script = get_completion_script("kctl-litellm", shell)
|
|
114
|
+
typer.echo(script)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _run() -> None:
|
|
118
|
+
"""Entry point with error handling."""
|
|
119
|
+
try:
|
|
120
|
+
app()
|
|
121
|
+
except LiteLLMError as e:
|
|
122
|
+
handle_cli_error(e)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
_run()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command modules for kctl-litellm."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Budget management commands for kctl-litellm.
|
|
2
|
+
|
|
3
|
+
Create and list LiteLLM budgets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich import print as rprint
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from kctl_litellm.core.callbacks import AppContext
|
|
15
|
+
from kctl_litellm.core.client import LiteLLMClient
|
|
16
|
+
from kctl_litellm.core.exceptions import LiteLLMError
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(help="Manage LiteLLM budgets.")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_client(actx: AppContext) -> LiteLLMClient:
|
|
22
|
+
cfg = actx.config
|
|
23
|
+
return LiteLLMClient(base_url=cfg.url, master_key=cfg.master_key)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command()
|
|
27
|
+
def create(
|
|
28
|
+
ctx: typer.Context,
|
|
29
|
+
budget_id: Annotated[str | None, typer.Option("--budget-id", help="Budget identifier.")] = None,
|
|
30
|
+
max_budget: Annotated[float, typer.Option("--max-budget", help="Maximum budget in USD.")] = 100.0,
|
|
31
|
+
soft_budget: Annotated[
|
|
32
|
+
float | None, typer.Option("--soft-budget", help="Soft budget limit (warning threshold).")
|
|
33
|
+
] = None,
|
|
34
|
+
max_parallel_requests: Annotated[
|
|
35
|
+
int | None, typer.Option("--max-parallel-requests", help="Max parallel requests.")
|
|
36
|
+
] = None,
|
|
37
|
+
tpm_limit: Annotated[int | None, typer.Option("--tpm-limit", help="Tokens per minute limit.")] = None,
|
|
38
|
+
rpm_limit: Annotated[int | None, typer.Option("--rpm-limit", help="Requests per minute limit.")] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Create a new budget.
|
|
41
|
+
|
|
42
|
+
Example: kctl-litellm budgets create --budget-id dev-budget --max-budget 200
|
|
43
|
+
"""
|
|
44
|
+
actx: AppContext = ctx.obj
|
|
45
|
+
out = actx.output
|
|
46
|
+
|
|
47
|
+
kwargs: dict = {"max_budget": max_budget}
|
|
48
|
+
if budget_id:
|
|
49
|
+
kwargs["budget_id"] = budget_id
|
|
50
|
+
if soft_budget is not None:
|
|
51
|
+
kwargs["soft_budget"] = soft_budget
|
|
52
|
+
if max_parallel_requests is not None:
|
|
53
|
+
kwargs["max_parallel_requests"] = max_parallel_requests
|
|
54
|
+
if tpm_limit is not None:
|
|
55
|
+
kwargs["tpm_limit"] = tpm_limit
|
|
56
|
+
if rpm_limit is not None:
|
|
57
|
+
kwargs["rpm_limit"] = rpm_limit
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
client = _get_client(actx)
|
|
61
|
+
result = client.create_budget(**kwargs)
|
|
62
|
+
client.close()
|
|
63
|
+
except LiteLLMError as exc:
|
|
64
|
+
out.error(str(exc))
|
|
65
|
+
raise typer.Exit(1) from exc
|
|
66
|
+
|
|
67
|
+
out.success("Budget created")
|
|
68
|
+
out.kv("Budget ID", result.get("budget_id", "[dim]auto-generated[/dim]"))
|
|
69
|
+
out.kv("Max Budget", f"${result.get('max_budget', max_budget)}")
|
|
70
|
+
if result.get("soft_budget") is not None:
|
|
71
|
+
out.kv("Soft Budget", f"${result['soft_budget']}")
|
|
72
|
+
if result.get("tpm_limit"):
|
|
73
|
+
out.kv("TPM Limit", str(result["tpm_limit"]))
|
|
74
|
+
if result.get("rpm_limit"):
|
|
75
|
+
out.kv("RPM Limit", str(result["rpm_limit"]))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command("list")
|
|
79
|
+
def list_(ctx: typer.Context) -> None:
|
|
80
|
+
"""List all budgets."""
|
|
81
|
+
actx: AppContext = ctx.obj
|
|
82
|
+
out = actx.output
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
client = _get_client(actx)
|
|
86
|
+
budgets = client.list_budgets()
|
|
87
|
+
client.close()
|
|
88
|
+
except LiteLLMError as exc:
|
|
89
|
+
out.error(str(exc))
|
|
90
|
+
raise typer.Exit(1) from exc
|
|
91
|
+
|
|
92
|
+
if not budgets:
|
|
93
|
+
out.warn("No budgets found.")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
table = Table(title="Budgets", show_header=True, header_style="bold cyan")
|
|
97
|
+
table.add_column("Budget ID", style="cyan")
|
|
98
|
+
table.add_column("Max Budget", justify="right")
|
|
99
|
+
table.add_column("Soft Budget", justify="right")
|
|
100
|
+
table.add_column("TPM Limit", justify="right")
|
|
101
|
+
table.add_column("RPM Limit", justify="right")
|
|
102
|
+
table.add_column("Max Parallel", justify="right")
|
|
103
|
+
|
|
104
|
+
for b in budgets:
|
|
105
|
+
if not isinstance(b, dict):
|
|
106
|
+
continue
|
|
107
|
+
budget_id = b.get("budget_id", "unknown")
|
|
108
|
+
max_b = f"${b['max_budget']:.2f}" if b.get("max_budget") is not None else "[dim]-[/dim]"
|
|
109
|
+
soft_b = f"${b['soft_budget']:.2f}" if b.get("soft_budget") is not None else "[dim]-[/dim]"
|
|
110
|
+
tpm = str(b.get("tpm_limit", "")) or "[dim]-[/dim]"
|
|
111
|
+
rpm = str(b.get("rpm_limit", "")) or "[dim]-[/dim]"
|
|
112
|
+
max_par = str(b.get("max_parallel_requests", "")) or "[dim]-[/dim]"
|
|
113
|
+
table.add_row(budget_id, max_b, soft_b, tpm, rpm, max_par)
|
|
114
|
+
|
|
115
|
+
rprint(table)
|
|
116
|
+
out.info(f"Total budgets: {len(budgets)}")
|