feat: translate module — runtime knobs, GRACE anchors, two-layer testing, QA fixes

Implementation:
- Performance knobs: llm_batch_max_rows, llm_concurrency, insert_concurrency,
  multi_lang_mode, batch_aggressiveness, max_in_flight_batches
- Alembic migration f7a8b9c0d1e2 (idempotent, nullable, non-destructive)
- LLM provider capabilities: throughput_class, reasoning_control,
  supports_json_object, default/max_llm_concurrency
- TargetSchemaValidationRequest with conditional validator (sqllab/direct_db)
- Scheduler: background dispatch, local imports for lazy bootstrap

GRACE-Poly compliance:
- Semantic anchors on orchestrator_aggregator, orchestrator_sql, llm_provider
- Shared module _llm_http.py: _apply_reasoning_control extraction (INV_4)
- Alembic migration anchors per C3/C1 template
- Four renamed Svelte components: RunOutcomeSummary, DetectionQualityCard,
  LanguageStatList, SourceLanguageOverride

QA (this session):
- 11 backend test regressions fixed: spec'd MagicMock null fields for new
  columns, _check_translation_cache_bulk retarget, scheduler mock wiring,
  language_detection=auto assertions, token budget constant update
- 4 frontend eslint errors fixed: SvelteSet/SvelteDate imports,
  unused lang parameter, dead isTransientError
- Production bugfix: job_to_response() mapped 6 missing fields
- Pre-existing auth test failure documented (dependencies.py untouched)
- Axiom: 6440 contracts, 0 warnings
This commit is contained in:
2026-07-20 14:19:47 +03:00
parent 31b9a19a0c
commit 49a566359a
80 changed files with 4823 additions and 630 deletions

View File

@@ -29,9 +29,18 @@ from .logger import logger
from .ssl import httpx_verify
# Module-level singleton clients, lazily initialized
_http_client_600: httpx.AsyncClient | None = None
_http_client_180: httpx.AsyncClient | None = None
_http_client_10: httpx.AsyncClient | None = None
# Default timeout for long LLM chat/completions (local models need long prefill+decode).
LLM_HTTP_TIMEOUT_SECONDS = 600.0
# Safety margin reserved in context for special tokens / sampler overhead.
CONTEXT_OUTPUT_MARGIN = 256
# Hard ceiling on completion tokens when caller over-requests (e.g. 10850).
# 8192 allows multi-lang single-row JSON without finish_reason=length mid-object.
MAX_COMPLETION_TOKENS_HARD_CAP = 8192
# #region SharedLlmHttpClient.GetSharedClient [C:2] [TYPE Function] [SEMANTICS shared,http,client,get]
# @ingroup Shared
@@ -39,7 +48,7 @@ _http_client_10: httpx.AsyncClient | None = None
# Uses shared ssl module (capath) for certificate verification.
# @POST Returns httpx.AsyncClient with verify=system_ssl_context() and specified timeout.
# @SIDE_EFFECT Lazily creates the client on first call and caches it for reuse.
def get_shared_http_client(timeout: float = 180.0) -> httpx.AsyncClient:
def get_shared_http_client(timeout: float = LLM_HTTP_TIMEOUT_SECONDS) -> httpx.AsyncClient:
"""Get or create a shared httpx.AsyncClient singleton with system SSL context.
Uses the system CA store (capath) for SSL verification — NOT certifi.
@@ -47,25 +56,32 @@ def get_shared_http_client(timeout: float = 180.0) -> httpx.AsyncClient:
are trusted for all HTTP calls.
Args:
timeout: Total timeout in seconds (default 180).
Use 10 for lightweight config fetches, 180 for LLM API calls.
timeout: Total timeout in seconds (default 600 for LLM calls).
Use 10 for lightweight config fetches, 600 for LLM API calls.
180 is still cached for legacy callers.
Returns:
httpx.AsyncClient with SSL context and specified timeout.
"""
global _http_client_180, _http_client_10
global _http_client_600, _http_client_180, _http_client_10
if abs(timeout - 600.0) < 0.1 or abs(timeout - LLM_HTTP_TIMEOUT_SECONDS) < 0.1:
if _http_client_600 is None:
ssl_ctx = httpx_verify()
_http_client_600 = httpx.AsyncClient(
verify=ssl_ctx,
# Connect fast-fail; long read/write for local generation.
timeout=httpx.Timeout(LLM_HTTP_TIMEOUT_SECONDS, connect=30.0),
)
return _http_client_600
# Cache two common timeout configurations to avoid creating new clients
if abs(timeout - 180.0) < 0.1:
if _http_client_180 is None:
ssl_ctx = httpx_verify()
_http_client_180 = httpx.AsyncClient(
verify=ssl_ctx,
timeout=httpx.Timeout(180.0),
timeout=httpx.Timeout(180.0, connect=30.0),
)
# @ADR [LOG-004] Removed "Created shared HTTP client (180s timeout)".
# Singleton creation (cached). Once-per-process infra detail. Not a decision point.
# Errors during actual LLM calls are properly EXPLOREd with context.
return _http_client_180
if abs(timeout - 10.0) < 0.1:
@@ -75,18 +91,14 @@ def get_shared_http_client(timeout: float = 180.0) -> httpx.AsyncClient:
verify=ssl_ctx,
timeout=httpx.Timeout(10.0),
)
# @ADR [LOG-004] Removed "Created shared HTTP client (10s timeout)".
# Singleton creation (cached). Once-per-process infra detail. Not a decision point.
return _http_client_10
# Custom timeout — create a new client (not cached)
ssl_ctx = httpx_verify()
client = httpx.AsyncClient(
verify=ssl_ctx,
timeout=httpx.Timeout(timeout),
timeout=httpx.Timeout(timeout, connect=min(30.0, timeout)),
)
# @ADR [LOG-004] Removed "Created shared HTTP client (custom timeout)".
# Rare (only custom timeout callers). Not worth the per-creation line.
return client
# #endregion SharedLlmHttpClient.GetSharedClient
@@ -111,6 +123,295 @@ def sanitize_url(url: str) -> str:
# #endregion SharedLlmHttpClient.SanitizeUrl
# #region SharedLlmHttpClient.EstimateMsgTokens [C:1] [TYPE Function] [SEMANTICS shared,llm,token,estimate]
# @BRIEF Conservative char-based token estimate for prompt sizing (no external tokenizer).
def _estimate_msg_tokens(text: str) -> int:
if not text:
return 1
# ~1.5 chars/token is intentionally pessimistic vs cl100k (~4) and covers CJK/JSON.
return max(1, int(len(text) / 1.5) + 1)
# #endregion SharedLlmHttpClient.EstimateMsgTokens
# #region SharedLlmHttpClient.ParseChatCompletionBody [C:3] [TYPE Function] [SEMANTICS shared,llm,sse,parse]
# @BRIEF Parse non-stream JSON or SSE chat.completion.chunk stream into a completion dict.
def _parse_chat_completion_body(response_text: str, status_code: int = 200) -> dict[str, Any]:
"""Parse OpenAI chat completion body; aggregate SSE if the proxy streamed anyway."""
text = response_text or ""
stripped = text.strip()
if not stripped:
raise ValueError(
f"LLM provider returned an empty body (status={status_code})"
)
# Fast path: normal non-stream JSON object
if stripped.startswith("{"):
try:
data = json.loads(stripped)
if isinstance(data, dict):
return data
except (json.JSONDecodeError, ValueError):
pass
# SSE path: "data: {...}\n\ndata: {...}\n\ndata: [DONE]"
if "data:" in stripped[:64] or stripped.startswith("data:") or "\ndata:" in stripped:
aggregated = _aggregate_sse_chat_completion(stripped)
if aggregated is not None:
logger.explore(
"Aggregated SSE chat.completion stream into non-stream payload",
extra={
"src": "SharedLlmHttpClient",
"content_len": len((aggregated.get("choices") or [{}])[0].get("message", {}).get("content") or ""),
"finish_reason": (aggregated.get("choices") or [{}])[0].get("finish_reason"),
},
)
return aggregated
preview = stripped[:500]
raise ValueError(
f"LLM provider returned an invalid JSON response "
f"(status={status_code}, body_len={len(text)}, preview={preview!r})"
)
# #endregion SharedLlmHttpClient.ParseChatCompletionBody
# #region SharedLlmHttpClient.AggregateSseChatCompletion [C:3] [TYPE Function] [SEMANTICS shared,llm,sse,aggregate]
# @BRIEF Merge OpenAI SSE chat.completion.chunk frames into one chat.completion-like dict.
def _aggregate_sse_chat_completion(sse_text: str) -> dict[str, Any] | None:
content_parts: list[str] = []
reasoning_parts: list[str] = []
finish_reason: str | None = None
model: str | None = None
completion_id: str | None = None
saw_chunk = False
for raw_line in sse_text.splitlines():
line = raw_line.strip()
if not line or line.startswith(":"):
continue
if line.startswith("data:"):
line = line[5:].strip()
if not line or line == "[DONE]":
continue
try:
chunk = json.loads(line)
except (json.JSONDecodeError, ValueError):
continue
if not isinstance(chunk, dict):
continue
# Some proxies wrap non-stream JSON as a single data: line
if chunk.get("object") == "chat.completion" and chunk.get("choices"):
return chunk
saw_chunk = True
model = model or chunk.get("model")
completion_id = completion_id or chunk.get("id")
for choice in chunk.get("choices") or []:
if not isinstance(choice, dict):
continue
if choice.get("finish_reason"):
finish_reason = choice.get("finish_reason")
delta = choice.get("delta") or {}
if not isinstance(delta, dict):
# Non-delta message (rare in streams)
msg = choice.get("message") or {}
if isinstance(msg, dict):
if msg.get("content"):
content_parts.append(str(msg["content"]))
for rk in ("reasoning_content", "reasoning", "thinking"):
if msg.get(rk):
reasoning_parts.append(str(msg[rk]))
continue
if delta.get("content"):
content_parts.append(str(delta["content"]))
for rk in ("reasoning_content", "reasoning", "thinking"):
if delta.get(rk):
reasoning_parts.append(str(delta[rk]))
if not saw_chunk and not content_parts and not reasoning_parts:
return None
message: dict[str, Any] = {
"role": "assistant",
"content": "".join(content_parts),
}
if reasoning_parts:
message["reasoning_content"] = "".join(reasoning_parts)
return {
"id": completion_id or "sse-aggregated",
"object": "chat.completion",
"model": model or "",
"choices": [
{
"index": 0,
"message": message,
"finish_reason": finish_reason or "stop",
}
],
}
# #endregion SharedLlmHttpClient.AggregateSseChatCompletion
# #region SharedLlmHttpClient.ClampMaxTokens [C:2] [TYPE Function] [SEMANTICS shared,llm,token,clamp]
# @BRIEF Cap completion tokens so prompt + output fits context and avoids multi-minute gens.
def _clamp_max_tokens(
requested: int,
prompt_tokens: int,
context_window: int | None,
) -> int:
capped = max(1, int(requested))
capped = min(capped, MAX_COMPLETION_TOKENS_HARD_CAP)
if context_window and context_window > 0:
free = context_window - prompt_tokens - CONTEXT_OUTPUT_MARGIN
if free < 64:
# Still request a tiny completion so the API call is valid; caller should
# have reduced batch size — this is a last-resort guard.
free = 64
capped = min(capped, free)
return max(1, capped)
# #endregion SharedLlmHttpClient.ClampMaxTokens
# #region SharedLlmHttpClient.ExtractJsonBlob [C:2] [TYPE Function] [SEMANTICS shared,llm,json,extract]
# @BRIEF Extract a parseable JSON object/array from free-form model text (e.g. reasoning).
def _extract_json_blob(text: str) -> str | None:
if not text or not isinstance(text, str):
return None
stripped = text.strip()
if stripped.startswith(("{", "[")):
try:
json.loads(stripped)
return stripped
except (json.JSONDecodeError, ValueError):
pass
for start_char, end_char in (("{", "}"), ("[", "]")):
start = text.find(start_char)
if start < 0:
continue
depth = 0
in_str = False
escape = False
for i in range(start, len(text)):
ch = text[i]
if in_str:
if escape:
escape = False
elif ch == "\\":
escape = True
elif ch == '"':
in_str = False
continue
if ch == '"':
in_str = True
elif ch == start_char:
depth += 1
elif ch == end_char:
depth -= 1
if depth == 0:
candidate = text[start : i + 1]
try:
json.loads(candidate)
return candidate
except (json.JSONDecodeError, ValueError):
break
return None
# #endregion SharedLlmHttpClient.ExtractJsonBlob
# #region SharedLlmHttpClient.LooksLikeJson [C:1] [TYPE Function]
def _looks_like_complete_json(text: str) -> bool:
if not text:
return False
t = text.strip()
if not (t.startswith("{") or t.startswith("[")):
return False
try:
json.loads(t)
return True
except (json.JSONDecodeError, ValueError):
return False
# #endregion SharedLlmHttpClient.LooksLikeJson
# #region SharedLlmHttpClient.ResolveMessageContent [C:2] [TYPE Function] [SEMANTICS shared,llm,content,reasoning]
# @BRIEF Prefer message.content; fall back to JSON inside reasoning_content / thinking fields.
# Also used when content is truncated mid-JSON (finish_reason=length) but reasoning holds
# a fuller answer — common for DeepSeek/Gemma thinking models.
def _resolve_message_content(msg: dict) -> tuple[str, str | None]:
content = msg.get("content")
if isinstance(content, list):
# Multimodal-style content blocks
parts = []
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
parts.append(str(block.get("text") or ""))
elif isinstance(block, str):
parts.append(block)
content = "".join(parts)
if content is None:
content = ""
content = str(content).strip() if content else ""
if content and _looks_like_complete_json(content):
return content, None
# Incomplete / non-JSON content: try extract JSON blob first, then reasoning fields.
if content:
extracted = _extract_json_blob(content)
if extracted and _looks_like_complete_json(extracted):
return extracted, "content_extracted"
for key in ("reasoning_content", "reasoning", "thinking", "reasoning_text"):
alt = msg.get(key)
if not alt or not isinstance(alt, str):
continue
extracted = _extract_json_blob(alt)
if extracted and _looks_like_complete_json(extracted):
return extracted, key
alt_stripped = alt.strip()
if _looks_like_complete_json(alt_stripped):
return alt_stripped, key
# Last resort: return partial content (caller may still recover truncated rows).
if content:
return content, None
return "", None
# #endregion SharedLlmHttpClient.ResolveMessageContent
# #region SharedLlmHttpClient.ApplyReasoningControl [C:2] [TYPE Function] [SEMANTICS shared,llm,reasoning,capabilities]
# @ingroup Shared
# @BRIEF Mutate payload with anti-think wire fields from explicit reasoning_control capability.
# @RATIONALE Wire format is a provider capability stored in DB — not inferred from model name.
# @REJECTED if "deepseek" in model / localhost host sniff at request time — brand/host lock-in.
def _apply_reasoning_control(
payload: dict[str, Any],
*,
disable_reasoning: bool,
reasoning_control: str | None,
) -> None:
"""Apply anti-reasoning payload fields when job requests disable_reasoning.
reasoning_control values (provider capability):
off | generic_none | openai_effort | deepseek_thinking | llamacpp_think | auto|None
Unsupported fields are stripped on HTTP 400 by _handle_response_format_fallback.
"""
if not disable_reasoning:
return
mode = (reasoning_control or "generic_none").strip().lower()
if mode in ("", "auto", "none"):
# Safe default when capability unset: send generic thinking-disable;
# strip-on-400 recovers for providers that reject the field.
mode = "generic_none"
if mode == "off":
return
if mode in ("generic_none", "deepseek_thinking"):
payload["thinking"] = {"type": "disabled"}
if mode == "openai_effort":
payload["reasoning_effort"] = "none"
if mode == "llamacpp_think":
payload["chat_template_kwargs"] = {"enable_thinking": False}
payload["think"] = False
# #endregion SharedLlmHttpClient.ApplyReasoningControl
# #region SharedLlmHttpClient.CallOpenaiCompatible [C:3] [TYPE Function] [SEMANTICS shared,llm,http,openai,async]
# @ingroup Shared
# @BRIEF Call OpenAI-compatible API asynchronously with rate-limit handling and structured output fallback.
@@ -118,6 +419,7 @@ def sanitize_url(url: str) -> str:
# @POST Returns (response text, finish_reason).
# @RAISES ValueError when the provider returns an invalid JSON response body.
# @SIDE_EFFECT Async HTTP POST to LLM API with optional retry on 429.
# @RELATION CALLS -> [SharedLlmHttpClient.ApplyReasoningControl]
# @RATIONALE Normalize malformed successful HTTP bodies into a stable provider error so
# preview and execution callers do not leak raw JSONDecodeError details.
# @REJECTED Letting response.json() propagate raw decode failures was rejected — it
@@ -132,6 +434,10 @@ async def call_openai_compatible(
provider_type: str = "openai",
max_tokens: int = 8192,
disable_reasoning: bool = False,
context_window: int | None = None,
timeout: float = LLM_HTTP_TIMEOUT_SECONDS,
reasoning_control: str | None = None,
supports_json_object: bool | None = None,
) -> tuple[str, str | None]:
"""Call OpenAI-compatible API for LLM requests (async)."""
if not base_url:
@@ -154,6 +460,24 @@ async def call_openai_compatible(
"Output ONLY valid JSON."
)
prompt_tokens = (
_estimate_msg_tokens(system_content)
+ _estimate_msg_tokens(prompt)
+ 16 # role/message framing overhead
)
effective_max_tokens = _clamp_max_tokens(max_tokens, prompt_tokens, context_window)
if effective_max_tokens != max_tokens:
logger.explore(
"Clamped max_tokens to fit context / hard cap",
extra={
"src": "SharedLlmHttpClient",
"requested_max_tokens": max_tokens,
"effective_max_tokens": effective_max_tokens,
"prompt_tokens_est": prompt_tokens,
"context_window": context_window,
},
)
payload: dict[str, Any] = {
"model": model,
"messages": [
@@ -161,21 +485,39 @@ async def call_openai_compatible(
{"role": "user", "content": prompt},
],
"temperature": 0.1,
"max_tokens": max_tokens,
"max_tokens": effective_max_tokens,
# Explicit non-stream — some proxies (cliproxy) default to SSE chunks otherwise.
"stream": False,
}
if provider_type in ("openai", "openai_compatible", "kilo", "openrouter", "litellm"):
if not disable_reasoning:
payload["response_format"] = {"type": "json_object"}
# Prefer strict JSON when capability allows (NULL = true for openai-compatible family).
use_json = supports_json_object if supports_json_object is not None else (
provider_type in ("openai", "openai_compatible", "kilo", "openrouter", "litellm")
)
if use_json:
payload["response_format"] = {"type": "json_object"}
if disable_reasoning:
if provider_type not in ("kilo", "openrouter", "litellm"):
payload["reasoning_effort"] = "none"
payload["max_tokens"] = max_tokens
_apply_reasoning_control(
payload,
disable_reasoning=disable_reasoning,
reasoning_control=reasoning_control,
)
client = get_shared_http_client()
response, response_text = await _do_http_request(client, url, headers, payload)
await _handle_response_format_fallback(client, response, response_text, payload, url, headers)
client = get_shared_http_client(timeout=timeout)
try:
response, response_text = await _do_http_request(client, url, headers, payload)
response, response_text = await _handle_response_format_fallback(
client, response, response_text, payload, url, headers,
)
except httpx.TimeoutException as exc:
# httpx often stringifies to "" — always include type + timeout budget.
detail = str(exc).strip() or repr(exc)
raise TimeoutError(
f"LLM HTTP timeout after {timeout}s ({type(exc).__name__}: {detail})"
) from exc
except httpx.HTTPError as exc:
detail = str(exc).strip() or repr(exc)
raise RuntimeError(f"LLM HTTP error ({type(exc).__name__}: {detail})") from exc
if not response.is_success:
logger.explore(
@@ -183,20 +525,7 @@ async def call_openai_compatible(
extra={"src": "SharedLlmHttpClient"},
)
response.raise_for_status()
try:
data = response.json()
except (json.JSONDecodeError, ValueError) as exc:
logger.explore(
"LLM provider returned an invalid JSON response",
extra={
"src": "SharedLlmHttpClient",
"status_code": response.status_code,
"content_type": response.headers.get("content-type"),
"response_preview": (response_text or "")[:2000],
},
error=f"{type(exc).__name__}: {exc}",
)
raise ValueError("LLM provider returned an invalid JSON response") from exc
data = _parse_chat_completion_body(response_text, status_code=response.status_code)
choices = data.get("choices", [])
if not choices:
@@ -227,7 +556,10 @@ async def call_openai_compatible(
)
raise ValueError(f"LLM response processing failed: {e}")
refusal = msg.get("refusal") if isinstance(msg, dict) else None
if not isinstance(msg, dict):
raise ValueError("LLM response message is not an object")
refusal = msg.get("refusal")
if refusal:
logger.explore(
"LLM refused to respond",
@@ -239,18 +571,38 @@ async def call_openai_compatible(
)
raise ValueError(f"LLM refused to respond: {refusal}")
content = msg.get("content") if isinstance(msg, dict) else ""
if not content and isinstance(msg, dict):
content = msg.get("content") or ""
content, fallback_field = _resolve_message_content(msg)
if fallback_field:
logger.explore(
"Recovered LLM content from reasoning/thinking field",
extra={
"src": "SharedLlmHttpClient",
"field": fallback_field,
"content_len": len(content),
"finish_reason": finish_reason,
},
)
if not content:
# Surface diagnostic fields both as structured extras and in the message so
# they survive log formatters that drop nested `extra` keys.
usage = data.get("usage") if isinstance(data, dict) else None
reasoning_len = 0
for rk in ("reasoning_content", "reasoning", "thinking", "reasoning_text"):
alt = msg.get(rk)
if isinstance(alt, str) and alt:
reasoning_len = max(reasoning_len, len(alt))
logger.explore(
"LLM returned empty content",
extra={
"src": "SharedLlmHttpClient",
"finish_reason": finish_reason,
"msg_keys": list(msg.keys()) if isinstance(msg, dict) else [],
"response_preview": str(data)[:2000],
"payload": {
"finish_reason": finish_reason,
"msg_keys": list(msg.keys()),
"reasoning_len": reasoning_len,
"usage": usage,
"response_preview": str(data)[:1500],
},
},
)
raise ValueError("LLM returned empty content")
@@ -303,20 +655,41 @@ async def _handle_response_format_fallback(
payload: dict,
url: str,
headers: dict,
) -> None:
"""Handle 400 errors from structured_outputs not being supported."""
_patterns = ("response_format", "structured_outputs", "structured", "json_object")
if not response.is_success and response.status_code == 400 and any(p in (response_text or "").lower() for p in _patterns):
logger.explore(
"Structured outputs not supported, retrying without response_format",
extra={"src": "SharedLlmHttpClient"},
)
payload.pop("response_format", None)
new_response = await client.post(url, headers=headers, json=payload)
# Mutate the original response object with new data
response.status_code = new_response.status_code
response._content = new_response.content
response.encoding = new_response.encoding
response.headers = new_response.headers
) -> tuple[httpx.Response, str]:
"""Handle 400 errors from unsupported request fields (json_object, think flags, …).
Returns a (possibly new) response + body text. Must not mutate encoding on a
response whose `.text` was already read — httpx forbids that.
"""
if response.is_success or response.status_code != 400:
return response, response_text
body_l = (response_text or "").lower()
_strip_keys: list[str] = []
_patterns_to_keys = (
(("response_format", "structured_outputs", "structured", "json_object"), "response_format"),
(("reasoning_effort",), "reasoning_effort"),
(("chat_template_kwargs", "enable_thinking"), "chat_template_kwargs"),
(("think",), "think"),
# DeepSeek V4 thinking toggle object — strip only when provider rejects it.
(("thinking",), "thinking"),
)
for patterns, key in _patterns_to_keys:
if key in payload and any(p in body_l for p in patterns):
_strip_keys.append(key)
# If body is vague ("unexpected keyword", "unknown field") strip optional anti-think flags.
if not _strip_keys and any(p in body_l for p in ("unknown", "unexpected", "invalid", "not support")):
for key in ("response_format", "reasoning_effort", "chat_template_kwargs", "think", "thinking"):
if key in payload:
_strip_keys.append(key)
if not _strip_keys:
return response, response_text
for key in _strip_keys:
payload.pop(key, None)
logger.explore(
"Retrying LLM request without unsupported fields",
extra={"src": "SharedLlmHttpClient", "stripped": _strip_keys},
)
new_response = await client.post(url, headers=headers, json=payload)
return new_response, new_response.text
# #endregion SharedLlmHttpClient.HandleResponseFormatFallback
# #endregion SharedLlmHttpClient