litellm-controller 0.1.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.
- litellm_controller/__init__.py +2 -0
- litellm_controller/client.py +88 -0
- litellm_controller/config.py +488 -0
- litellm_controller/examples/deepseek.py +87 -0
- litellm_controller/examples/openrouter.py +118 -0
- litellm_controller/examples/type_static_json.py +38 -0
- litellm_controller/main.py +110 -0
- litellm_controller/modules/__init__.py +1 -0
- litellm_controller/modules/metadata.py +1284 -0
- litellm_controller/modules/models.py +1018 -0
- litellm_controller/modules/routing.py +785 -0
- litellm_controller/modules/settings.py +277 -0
- litellm_controller/ui.py +337 -0
- litellm_controller/upstreams.py +95 -0
- litellm_controller-0.1.0.dist-info/METADATA +90 -0
- litellm_controller-0.1.0.dist-info/RECORD +19 -0
- litellm_controller-0.1.0.dist-info/WHEEL +5 -0
- litellm_controller-0.1.0.dist-info/entry_points.txt +2 -0
- litellm_controller-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# --- LITELLMCTL CONFIG ---
|
|
3
|
+
NAME = "OpenRouter 实时元数据"
|
|
4
|
+
DESCRIPTION = "从 OpenRouter 公共 API 拉取最新模型计费与上下文数据"
|
|
5
|
+
PRIORITY = 10
|
|
6
|
+
ENABLED = False
|
|
7
|
+
# -------------------------
|
|
8
|
+
"""OpenRouter 元数据生成脚本(litellm-controller 示例脚本)。
|
|
9
|
+
|
|
10
|
+
从 OpenRouter 公共 API 拉取模型价格/能力数据(无需 API key),
|
|
11
|
+
转换为 litellm model_prices_and_context_window.json 的分片格式输出到 stdout。
|
|
12
|
+
|
|
13
|
+
约定(litellm-controller auto 分片脚本要求):
|
|
14
|
+
- stdout 只输出 JSON 分片({"模型key": {字段...}}),日志一律走 stderr
|
|
15
|
+
- 失败时以非零退出码退出(构建将中止)
|
|
16
|
+
|
|
17
|
+
单独运行:
|
|
18
|
+
python openrouter.py
|
|
19
|
+
"""
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
import urllib.request
|
|
23
|
+
|
|
24
|
+
API_URL = "https://openrouter.ai/api/v1/models"
|
|
25
|
+
PROVIDER = "openrouter"
|
|
26
|
+
TIMEOUT = 30
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _float_or_none(value):
|
|
30
|
+
if value in (None, ""):
|
|
31
|
+
return None
|
|
32
|
+
try:
|
|
33
|
+
return float(value)
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
try:
|
|
40
|
+
req = urllib.request.Request(
|
|
41
|
+
API_URL, headers={"User-Agent": "litellm-controller/openrouter"}
|
|
42
|
+
)
|
|
43
|
+
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
|
|
44
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(f"拉取 OpenRouter 模型列表失败: {e}", file=sys.stderr)
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
models = payload.get("data")
|
|
50
|
+
if not isinstance(models, list) or not models:
|
|
51
|
+
print("OpenRouter 返回了空的模型列表", file=sys.stderr)
|
|
52
|
+
return 1
|
|
53
|
+
|
|
54
|
+
fragment = {}
|
|
55
|
+
skipped = 0
|
|
56
|
+
for m in models:
|
|
57
|
+
mid = m.get("id")
|
|
58
|
+
if not isinstance(mid, str) or not mid:
|
|
59
|
+
skipped += 1
|
|
60
|
+
continue
|
|
61
|
+
arch = m.get("architecture") or {}
|
|
62
|
+
in_mods = arch.get("input_modalities") or []
|
|
63
|
+
out_mods = arch.get("output_modalities") or []
|
|
64
|
+
# 只保留文本进/文本出的 chat 模型,过滤 embedding/图像/语音等
|
|
65
|
+
if "text" not in in_mods or "text" not in out_mods:
|
|
66
|
+
skipped += 1
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
pricing = m.get("pricing") or {}
|
|
70
|
+
in_cost = _float_or_none(pricing.get("prompt"))
|
|
71
|
+
out_cost = _float_or_none(pricing.get("completion"))
|
|
72
|
+
if in_cost is None or out_cost is None:
|
|
73
|
+
skipped += 1
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
entry = {
|
|
77
|
+
"litellm_provider": PROVIDER,
|
|
78
|
+
"mode": "chat",
|
|
79
|
+
"input_cost_per_token": in_cost,
|
|
80
|
+
"output_cost_per_token": out_cost,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cache_read = _float_or_none(pricing.get("input_cache_read"))
|
|
84
|
+
if cache_read is not None:
|
|
85
|
+
entry["cache_read_input_token_cost"] = cache_read
|
|
86
|
+
|
|
87
|
+
ctx = m.get("context_length")
|
|
88
|
+
if isinstance(ctx, int) and ctx > 0:
|
|
89
|
+
entry["max_input_tokens"] = ctx
|
|
90
|
+
|
|
91
|
+
tp = m.get("top_provider") or {}
|
|
92
|
+
max_out = tp.get("max_completion_tokens")
|
|
93
|
+
if isinstance(max_out, int) and max_out > 0:
|
|
94
|
+
entry["max_output_tokens"] = max_out
|
|
95
|
+
entry["max_tokens"] = max_out
|
|
96
|
+
|
|
97
|
+
params = m.get("supported_parameters") or []
|
|
98
|
+
entry["supports_function_calling"] = "tools" in params
|
|
99
|
+
entry["supports_vision"] = "image" in in_mods
|
|
100
|
+
entry["supports_prompt_caching"] = cache_read is not None
|
|
101
|
+
entry["supports_response_schema"] = (
|
|
102
|
+
"response_format" in params or "structured_outputs" in params
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
fragment[mid] = entry
|
|
106
|
+
|
|
107
|
+
print(
|
|
108
|
+
f"OpenRouter: 共 {len(models)} 个模型,生成 {len(fragment)} 个 chat 模型条目,"
|
|
109
|
+
f"跳过 {skipped} 个(非文本/缺价格)",
|
|
110
|
+
file=sys.stderr,
|
|
111
|
+
)
|
|
112
|
+
json.dump(fragment, sys.stdout, ensure_ascii=False, indent=2)
|
|
113
|
+
sys.stdout.write("\n")
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
sys.exit(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# --- LITELLMCTL CONFIG ---
|
|
3
|
+
NAME = "静态 JSON 字典示例"
|
|
4
|
+
DESCRIPTION = "纯 Python 字典内联静态元数据示例,可作为纯文本或定制化配置模版"
|
|
5
|
+
PRIORITY = 20
|
|
6
|
+
ENABLED = False
|
|
7
|
+
# -------------------------
|
|
8
|
+
"""静态 JSON 字典示例脚本。
|
|
9
|
+
|
|
10
|
+
可以直接在此脚本中内嵌特定模型的价格与上下文配置。
|
|
11
|
+
输出规范:将字典以 JSON 格式打印至标准输出(stdout),日志/提示输出至标准错误(stderr)。
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
DATA = {
|
|
17
|
+
"my-custom-model": {
|
|
18
|
+
"max_tokens": 4096,
|
|
19
|
+
"max_input_tokens": 8192,
|
|
20
|
+
"max_output_tokens": 4096,
|
|
21
|
+
"input_cost_per_token": 0.000001,
|
|
22
|
+
"output_cost_per_token": 0.000002,
|
|
23
|
+
"litellm_provider": "openai",
|
|
24
|
+
"mode": "chat",
|
|
25
|
+
"supports_function_calling": True,
|
|
26
|
+
"supports_vision": False,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
json.dump(DATA, sys.stdout, ensure_ascii=False, indent=2)
|
|
33
|
+
sys.stdout.write("\n")
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
sys.exit(main())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from InquirerPy import prompt
|
|
8
|
+
from InquirerPy.base.control import Choice
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
from .config import ensure_config_ready, load_config
|
|
12
|
+
from .modules.metadata import sync_example_scripts, generate_metadata
|
|
13
|
+
from .modules.models import models_module
|
|
14
|
+
from .modules.routing import routing_module
|
|
15
|
+
from .modules.settings import settings_module
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _setup_logging():
|
|
19
|
+
logging.getLogger().setLevel(logging.CRITICAL)
|
|
20
|
+
for name in ("urllib3", "requests", "InquirerPy", "charset_normalizer"):
|
|
21
|
+
logging.getLogger(name).setLevel(logging.CRITICAL)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_args():
|
|
25
|
+
parser = argparse.ArgumentParser(description="LiteLLM 管理工具")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-D",
|
|
28
|
+
"--dir",
|
|
29
|
+
help="配置文件目录路径",
|
|
30
|
+
default=os.path.expanduser("~/.config/litellm-controller"),
|
|
31
|
+
)
|
|
32
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
33
|
+
gen_parser = subparsers.add_parser(
|
|
34
|
+
"metadata-gen",
|
|
35
|
+
help="执行构建流程并导出 model_prices_and_context_window.json(非交互)",
|
|
36
|
+
)
|
|
37
|
+
gen_parser.add_argument(
|
|
38
|
+
"path",
|
|
39
|
+
nargs="?",
|
|
40
|
+
default=None,
|
|
41
|
+
help="输出目录(可选,仅目录;缺省使用 config.yaml 的 output_file,"
|
|
42
|
+
"文件名固定为 model_prices_and_context_window.json)",
|
|
43
|
+
)
|
|
44
|
+
return parser.parse_args()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
_setup_logging()
|
|
49
|
+
args = parse_args()
|
|
50
|
+
os.environ["LITELLM_CONTROLLER_CONFIG_DIR"] = args.dir
|
|
51
|
+
if not ensure_config_ready():
|
|
52
|
+
print("未完成配置,程序退出。")
|
|
53
|
+
return 1
|
|
54
|
+
|
|
55
|
+
# 启动时自动同步示例脚本与默认 default.py
|
|
56
|
+
try:
|
|
57
|
+
sync_example_scripts()
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
if args.command == "metadata-gen":
|
|
62
|
+
config = load_config()
|
|
63
|
+
out_dir = Path(args.path).expanduser() if args.path else None
|
|
64
|
+
try:
|
|
65
|
+
generate_metadata(config, out_dir)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"构建失败: {e}")
|
|
68
|
+
return 1
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
print(f"LiteLLM 管理工具 v{__version__}")
|
|
72
|
+
print("=" * 30)
|
|
73
|
+
while True:
|
|
74
|
+
questions = [
|
|
75
|
+
{
|
|
76
|
+
"type": "list",
|
|
77
|
+
"message": "请选择要执行的功能:",
|
|
78
|
+
"choices": [
|
|
79
|
+
Choice("manage_models", name="1. 模型管理"),
|
|
80
|
+
Choice("manage_routing", name="2. 路由管理"),
|
|
81
|
+
Choice("settings", name="3. 设置"),
|
|
82
|
+
Choice(value=None, name="[退出]"),
|
|
83
|
+
],
|
|
84
|
+
"name": "action",
|
|
85
|
+
"long_instruction": "↑↓ 移动 · 回车 确认 · Ctrl+C 退出",
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
try:
|
|
89
|
+
result = prompt(questions)
|
|
90
|
+
if not result:
|
|
91
|
+
print("\n已退出。")
|
|
92
|
+
break
|
|
93
|
+
action = result.get("action")
|
|
94
|
+
if action == "manage_models":
|
|
95
|
+
models_module()
|
|
96
|
+
elif action == "manage_routing":
|
|
97
|
+
routing_module()
|
|
98
|
+
elif action == "settings":
|
|
99
|
+
settings_module()
|
|
100
|
+
elif action is None:
|
|
101
|
+
print("\n已退出。")
|
|
102
|
+
break
|
|
103
|
+
except KeyboardInterrupt:
|
|
104
|
+
print("\n已退出。")
|
|
105
|
+
break
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""业务模块。"""
|