feat(tools-pve/asr-pipeline): multi-backend ASR web app with LiteLLM routing
This commit is contained in:
@@ -1,19 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""HTTPS client for the model provider. No agent CLI, no SDK.
|
||||
"""HTTPS client for LiteLLM / OpenAI-compatible model gateways. No agent CLI, no SDK.
|
||||
|
||||
Deliberately built on urllib so the program has zero third-party HTTP
|
||||
dependencies: it must keep working inside a bare systemd unit years from now.
|
||||
|
||||
Behaviours encoded from measurement rather than guesswork:
|
||||
* concurrency 6 (HTTP 429 appears at ~12)
|
||||
* HTTP 402 "Payment Required" is transient on ZenMux — credit seems reserved
|
||||
against max_tokens, so a burst of large-budget calls can trip it and then
|
||||
succeed moments later; it gets a much longer backoff than a rate limit
|
||||
* `temperature` is rejected outright by claude-sonnet-5.5 / opus-5 but accepted by
|
||||
haiku-4.5, sonnet-4.6, gemini and qwen, so it cannot be inferred from the
|
||||
vendor prefix; the client self-heals on the 400 and remembers
|
||||
* an empty completion (reasoning models burning the whole budget on hidden
|
||||
thoughts) is retried rather than silently propagated as ""
|
||||
Built on urllib so the program has zero third-party HTTP dependencies.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -30,14 +18,22 @@ from .config import CFG
|
||||
from .log import log
|
||||
|
||||
PRICES = {
|
||||
"aistudio/gemini-flash-lite-latest": (0.3, 2.5),
|
||||
"vertex/gemini-flash-lite-latest": (0.3, 2.5),
|
||||
"gemini-flash-lite-latest": (0.3, 2.5),
|
||||
"google/gemini-3.5-flash-lite": (0.3, 2.5),
|
||||
"google/gemini-3.5-flash": (1.5, 9.0),
|
||||
"aistudio/gemini-flash-latest": (1.5, 9.0),
|
||||
"vertex/gemini-flash-latest": (1.5, 9.0),
|
||||
"aistudio/gemini-pro-latest": (2.5, 12.0),
|
||||
"codex/gpt-sol-latest": (2.0, 8.0),
|
||||
"openai/gpt-sol-latest": (2.0, 8.0),
|
||||
"codex/gpt-luna-latest": (1.5, 6.0),
|
||||
"xai/grok-latest": (2.0, 10.0),
|
||||
"anthropic/claude-haiku-4.5": (1.0, 5.0),
|
||||
"anthropic/claude-sonnet-5": (2.0, 10.0),
|
||||
"anthropic/claude-sonnet-5.5": (2.0, 10.0),
|
||||
"anthropic/claude-sonnet-4.6": (3.0, 15.0),
|
||||
"anthropic/claude-opus-5": (5.0, 25.0),
|
||||
"qwen/qwen3.7-plus": (0.1373076, 0.5492304),
|
||||
}
|
||||
|
||||
_sem = threading.BoundedSemaphore(CFG.concurrency)
|
||||
@@ -45,6 +41,8 @@ _NO_TEMPERATURE: set[str] = {
|
||||
"anthropic/claude-sonnet-5",
|
||||
"anthropic/claude-sonnet-5.5",
|
||||
"anthropic/claude-opus-5",
|
||||
"codex/gpt-sol-latest",
|
||||
"codex/gpt-luna-latest",
|
||||
}
|
||||
_lock = threading.Lock()
|
||||
_key: list[str] = []
|
||||
@@ -143,7 +141,7 @@ def chat(model: str, prompt: str, *, system: str | None = None,
|
||||
except Exception:
|
||||
pass
|
||||
last = f"HTTP{e.code}: {detail[:200]}"
|
||||
if e.code == 400 and "temperature" in detail:
|
||||
if e.code == 400 and "temperature" in detail.lower():
|
||||
with _lock:
|
||||
_NO_TEMPERATURE.add(model)
|
||||
payload = payload_of()
|
||||
@@ -151,13 +149,12 @@ def chat(model: str, prompt: str, *, system: str | None = None,
|
||||
cpath = _cache_path(model, payload)
|
||||
retries += 1
|
||||
continue
|
||||
if e.code not in (402, 408, 429, 500, 502, 503, 504):
|
||||
if e.code not in (408, 429, 500, 502, 503, 504):
|
||||
if usage:
|
||||
usage.failures += 1
|
||||
raise RuntimeError(f"{model} {last}") from e
|
||||
retries += 1
|
||||
delay = (min(20 * (attempt + 1), 120) if e.code == 402
|
||||
else min(2 ** attempt + random.uniform(0, 1.5), 45))
|
||||
delay = min(2 ** attempt + random.uniform(0, 1.5), 45)
|
||||
log(f"llm retry {attempt+1}/{CFG.max_retries} {model} {last} "
|
||||
f"sleep {delay:.0f}s", level="WARN")
|
||||
time.sleep(delay)
|
||||
@@ -203,8 +200,6 @@ def chat_many(model: str, prompts: list[str], **kw) -> list[str]:
|
||||
except BaseException as e: # noqa: BLE001 - recorded, decided by caller
|
||||
errors.append((i, e))
|
||||
if errors:
|
||||
# Partial failure must not silently yield empty chunks: the caller falls
|
||||
# back to the original text for these indices, which the gate then flags.
|
||||
for i, e in errors:
|
||||
log(f"chunk {i} failed: {e}", level="ERROR")
|
||||
raise PartialFailure(out, [i for i, _ in errors])
|
||||
|
||||
Reference in New Issue
Block a user