diff --git a/.github/time/test_time.py b/.github/time/test_time.py index 887c915..061151c 100644 --- a/.github/time/test_time.py +++ b/.github/time/test_time.py @@ -583,3 +583,23 @@ assert vt.external_refs('
  • 429: overloaded"), ConnectionResetError("reset"), + {"choices": [{"message": {"content": "fine"}}]}] +def flaky_http(method, url, token, body=None, timeout=0): + x = seq.pop(0) + if isinstance(x, Exception): + raise x + return x +tm.http, os.environ["VENICE_API_KEY"] = flaky_http, "vk" +real_think = importlib.util.module_from_spec(spec); spec.loader.exec_module(real_think) +real_think.http = flaky_http +assert real_think.think("grok", None, None, "s", "u") == "fine" and seq == [] +real_think.http = lambda *a, **k: (_ for _ in ()).throw(RuntimeError("POST x -> 401: bad key")) +try: + real_think.think("grok", None, None, "s", "u"); raise AssertionError("a 401 must not be retried") +except RuntimeError as e: + assert "401" in str(e) +print("ALL OK 14") diff --git a/.github/time/time.py b/.github/time/time.py index 1d7ab6d..4fd5ea1 100644 --- a/.github/time/time.py +++ b/.github/time/time.py @@ -14,6 +14,7 @@ import json import os import re import sys +import time import urllib.error import urllib.request from pathlib import Path @@ -100,16 +101,32 @@ def mentions(text: str) -> dict[str, tuple[str | None, str | None]]: return out -def think(seat: str, model: str | None, effort: str | None, system: str, user: str) -> str: +def venice_chat(body: dict, timeout: float) -> dict: + """POST chat/completions, riding out Venice's brief overloads (429, 5xx) and dropped connections with backoff.""" key = os.environ.get("VENICE_API_KEY") if not key: raise LookupError("no VENICE_API_KEY secret on this repo yet") + for attempt in range(4): + try: + return http("POST", f"{VENICE}/chat/completions", key, body, timeout=timeout) + except (RuntimeError, OSError) as e: + transient = isinstance(e, OSError) or re.search(r"-> (429|5\d\d)\b", str(e)) + if attempt == 3 or not transient: + raise + print(f"venice busy ({str(e)[:80]}); retrying") + time.sleep(int(os.environ.get("TIME_VENICE_BACKOFF") or 15) * (attempt + 1) ** 2) + raise AssertionError("unreachable") + + +def think(seat: str, model: str | None, effort: str | None, system: str, user: str) -> str: + if not os.environ.get("VENICE_API_KEY"): + raise LookupError("no VENICE_API_KEY secret on this repo yet") body = {"model": model or SEATS[seat]["default"], "temperature": 0.4, "max_tokens": 4000, "messages": [{"role": "system", "content": system}, {"role": "user", "content": user}], "venice_parameters": {"include_venice_system_prompt": False}} if effort: body["reasoning_effort"] = effort - out = http("POST", f"{VENICE}/chat/completions", key, body, timeout=240) + out = venice_chat(body, timeout=240) text = out["choices"][0]["message"]["content"] or "" return re.sub(r".*?", "", text, flags=re.S).strip() or "(empty reply)" @@ -483,7 +500,7 @@ def venice_work(card: dict, done: str, spec: str) -> dict: "venice_parameters": {"include_venice_system_prompt": False}} if effort: body["reasoning_effort"] = effort - msg = http("POST", f"{VENICE}/chat/completions", key, body, timeout=300)["choices"][0]["message"] + msg = venice_chat(body, timeout=300)["choices"][0]["message"] msgs.append({k: v for k, v in msg.items() if k in ("role", "content", "tool_calls")}) calls = msg.get("tool_calls") or [] if not calls: