feat(tools-pve/asr-pipeline): multi-backend ASR web app with LiteLLM routing
Build image / build (push) Canceled after 0s
Build ASR Pipeline / image (push) Canceled after 0s

This commit is contained in:
Codex
2026-11-01 22:07:57 -08:00
parent d1790a05b0
commit 2367c5eb4e
23 changed files with 2462 additions and 451 deletions
+17 -22
View File
@@ -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])