Update sync_cliproxy_cleanup.py
Browse files- sync_cliproxy_cleanup.py +63 -14
sync_cliproxy_cleanup.py
CHANGED
|
@@ -11,24 +11,71 @@ LITELLM_MASTER_KEY = os.environ["LITELLM_MASTER_KEY"]
|
|
| 11 |
CREDENTIAL_NAME = os.environ["LITELLM_CREDENTIAL_NAME"] # LiteLLM 中预先创建好的凭证名称
|
| 12 |
PRIMARY_MODEL_GROUP = os.environ.get("FALLBACK_PRIMARY_MODEL", "cliproxy/*")
|
| 13 |
SYNC_INTERVAL = int(os.environ.get("SYNC_INTERVAL_SECONDS", 600)) # 默认10分钟
|
|
|
|
|
|
|
| 14 |
|
| 15 |
SYNC_TAG = "cliproxy-synced"
|
| 16 |
MASTER_HEADERS = {"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}
|
| 17 |
|
| 18 |
-
# ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
def get_cliproxy_credential() -> Optional[Dict[str, str]]:
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
if not
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
# ---------- CLIProxy 模型获取 ----------
|
| 34 |
def get_cliproxy_models(cred: dict) -> List[Dict]:
|
|
@@ -64,7 +111,7 @@ def delete_model(model_name: str):
|
|
| 64 |
)
|
| 65 |
return resp.status_code
|
| 66 |
|
| 67 |
-
def add_model_to_litellm(model_data: Dict):
|
| 68 |
original_id = model_data["id"]
|
| 69 |
owner = model_data.get("owned_by", "unknown")
|
| 70 |
new_name = f"{owner}/{original_id}" if not original_id.startswith(f"{owner}/") else original_id
|
|
@@ -153,6 +200,8 @@ def sync():
|
|
| 153 |
|
| 154 |
# ---------- 守护进程入口 ----------
|
| 155 |
if __name__ == "__main__":
|
|
|
|
|
|
|
| 156 |
while True:
|
| 157 |
sync()
|
| 158 |
time.sleep(SYNC_INTERVAL)
|
|
|
|
| 11 |
CREDENTIAL_NAME = os.environ["LITELLM_CREDENTIAL_NAME"] # LiteLLM 中预先创建好的凭证名称
|
| 12 |
PRIMARY_MODEL_GROUP = os.environ.get("FALLBACK_PRIMARY_MODEL", "cliproxy/*")
|
| 13 |
SYNC_INTERVAL = int(os.environ.get("SYNC_INTERVAL_SECONDS", 600)) # 默认10分钟
|
| 14 |
+
MAX_RETRIES = int(os.environ.get("MAX_CREDENTIAL_RETRIES", 5))
|
| 15 |
+
RETRY_DELAY = int(os.environ.get("RETRY_DELAY_SECONDS", 5))
|
| 16 |
|
| 17 |
SYNC_TAG = "cliproxy-synced"
|
| 18 |
MASTER_HEADERS = {"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}
|
| 19 |
|
| 20 |
+
# ---------- 辅助函数:等待 LiteLLM 完全就绪 ----------
|
| 21 |
+
def wait_for_litellm_ready(timeout: int = 180):
|
| 22 |
+
"""轮询 /health 直到 200,超时则报错"""
|
| 23 |
+
start = time.time()
|
| 24 |
+
print("⏳ 等待 LiteLLM Proxy 启动...")
|
| 25 |
+
while time.time() - start < timeout:
|
| 26 |
+
try:
|
| 27 |
+
resp = requests.get(f"{LITELLM_BASE_URL}/health", headers=MASTER_HEADERS, timeout=5)
|
| 28 |
+
if resp.status_code == 200:
|
| 29 |
+
print("✅ LiteLLM Proxy 已就绪")
|
| 30 |
+
return
|
| 31 |
+
except requests.RequestException:
|
| 32 |
+
pass
|
| 33 |
+
time.sleep(5)
|
| 34 |
+
raise RuntimeError(f"❌ LiteLLM Proxy 未在 {timeout}s 内就绪,请检查服务状态")
|
| 35 |
+
|
| 36 |
+
# ---------- 凭证获取(带重试)----------
|
| 37 |
def get_cliproxy_credential() -> Optional[Dict[str, str]]:
|
| 38 |
+
"""获取指定凭证,支持重试,防止数据库尚未加载完成"""
|
| 39 |
+
last_exception = None
|
| 40 |
+
for attempt in range(1, MAX_RETRIES + 1):
|
| 41 |
+
try:
|
| 42 |
+
resp = requests.get(f"{LITELLM_BASE_URL}/credentials", headers=MASTER_HEADERS)
|
| 43 |
+
resp.raise_for_status()
|
| 44 |
+
credentials = resp.json().get("data", [])
|
| 45 |
+
# 兼容有时直接返回数组的情况
|
| 46 |
+
if not isinstance(credentials, list):
|
| 47 |
+
credentials = resp.json() if isinstance(resp.json(), list) else []
|
| 48 |
+
|
| 49 |
+
for cred in credentials:
|
| 50 |
+
if cred.get("credential_name") == CREDENTIAL_NAME:
|
| 51 |
+
cred_info = cred.get("credential_info", {})
|
| 52 |
+
api_base = cred_info.get("api_base", "")
|
| 53 |
+
api_key = cred_info.get("api_key", "")
|
| 54 |
+
if not api_base or not api_key:
|
| 55 |
+
raise ValueError(f"凭证 '{CREDENTIAL_NAME}' 中缺少 api_base 或 api_key")
|
| 56 |
+
print(f"🔑 成功获取凭证 '{CREDENTIAL_NAME}'")
|
| 57 |
+
return {"api_base": api_base, "api_key": api_key}
|
| 58 |
+
|
| 59 |
+
print(f"⚠️ 第 {attempt}/{MAX_RETRIES} 次查找凭证 '{CREDENTIAL_NAME}' 未找到,等待 {RETRY_DELAY}s 重试...")
|
| 60 |
+
last_exception = None
|
| 61 |
+
time.sleep(RETRY_DELAY)
|
| 62 |
+
|
| 63 |
+
except requests.RequestException as e:
|
| 64 |
+
print(f"⚠️ 第 {attempt}/{MAX_RETRIES} 次请求凭证列表失败: {e}")
|
| 65 |
+
last_exception = e
|
| 66 |
+
time.sleep(RETRY_DELAY)
|
| 67 |
+
except ValueError as e:
|
| 68 |
+
# 凭证结构错误,直接抛出
|
| 69 |
+
raise e
|
| 70 |
+
|
| 71 |
+
# 所有重试失败
|
| 72 |
+
if last_exception:
|
| 73 |
+
raise RuntimeError(f"❌ 无法获取凭证列表,网络或服务异常") from last_exception
|
| 74 |
+
raise ValueError(
|
| 75 |
+
f"❌ 未找到名为 '{CREDENTIAL_NAME}' 的凭证。\n"
|
| 76 |
+
f" 请确认凭证已在 UI 中创建,且 Secret 'LITELLM_SALT_KEY' 已设置为固定值。\n"
|
| 77 |
+
f" 如仍有问题,请连接数据库执行清理:DELETE FROM \"LiteLLM_VerificationToken\"; 后重启 LiteLLM。"
|
| 78 |
+
)
|
| 79 |
|
| 80 |
# ---------- CLIProxy 模型获取 ----------
|
| 81 |
def get_cliproxy_models(cred: dict) -> List[Dict]:
|
|
|
|
| 111 |
)
|
| 112 |
return resp.status_code
|
| 113 |
|
| 114 |
+
def add_model_to_litellm(model_data: Dict) -> (bool, dict):
|
| 115 |
original_id = model_data["id"]
|
| 116 |
owner = model_data.get("owned_by", "unknown")
|
| 117 |
new_name = f"{owner}/{original_id}" if not original_id.startswith(f"{owner}/") else original_id
|
|
|
|
| 200 |
|
| 201 |
# ---------- 守护进程入口 ----------
|
| 202 |
if __name__ == "__main__":
|
| 203 |
+
wait_for_litellm_ready() # 启动前等待 LiteLLM 完全就绪
|
| 204 |
+
print("开始守护同步任务...")
|
| 205 |
while True:
|
| 206 |
sync()
|
| 207 |
time.sleep(SYNC_INTERVAL)
|