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.
@@ -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
+ """业务模块。"""