fix: drop blank system text blocks before litellm dispatch

This commit is contained in:
9qeklajc
2026-09-29 20:54:37 +02:00
parent af2cf4bb6b
commit 32d93e00e2
2 changed files with 66 additions and 0 deletions
+30
View File
@@ -84,6 +84,34 @@ ALLOWED_MESSAGES_REQUEST_FIELDS: frozenset[str] = frozenset(
)
def prune_blank_system_blocks(body: dict) -> None:
"""Drop whitespace-only ``system`` text.
Anthropic accepts a blank system prompt; OpenAI-compatible upstreams
reject it with ``text content blocks must contain non-whitespace text``.
"""
system = body.get("system")
if isinstance(system, str):
if not system.strip():
body.pop("system", None)
return
if not isinstance(system, list):
return
kept = [
block
for block in system
if not (
isinstance(block, dict)
and block.get("type") == "text"
and not str(block.get("text") or "").strip()
)
]
if kept:
body["system"] = kept
else:
body.pop("system", None)
def coerce_litellm_payload(payload: object) -> dict:
"""Convert a litellm event into a plain dict.
@@ -511,6 +539,8 @@ async def dispatch_anthropic_messages(
)
body = {k: v for k, v in body.items() if k in ALLOWED_MESSAGES_REQUEST_FIELDS}
prune_blank_system_blocks(body)
model_suffix = adapt_request(body) if adapt_request else ""
# LiteLLM turns Anthropic's server-side web_search tool into the OpenAI
@@ -23,6 +23,9 @@ from routstr.core.db import ApiKey # noqa: E402
from routstr.payment.cost_calculation import CostData # noqa: E402
from routstr.payment.models import Architecture, Model, Pricing # noqa: E402
from routstr.upstream.base import BaseUpstreamProvider # noqa: E402
from routstr.upstream.messages_dispatch import ( # noqa: E402
prune_blank_system_blocks,
)
from routstr.wallet import MintConnectionError, TokenConsumedError # noqa: E402
# ---------------------------------------------------------------------------
@@ -113,6 +116,39 @@ def _make_request(request_id: str | None = "req-test") -> Any:
# ---------------------------------------------------------------------------
def test_prune_blank_system_blocks_drops_blank_blocks() -> None:
body = {
"system": [
{"type": "text", "text": " \n"},
{"type": "text", "text": "real prompt"},
]
}
prune_blank_system_blocks(body)
assert body["system"] == [{"type": "text", "text": "real prompt"}]
def test_prune_blank_system_blocks_drops_key_when_all_blank() -> None:
body = {"system": [{"type": "text", "text": "\n"}], "max_tokens": 8}
prune_blank_system_blocks(body)
assert body == {"max_tokens": 8}
def test_prune_blank_system_blocks_handles_string_system() -> None:
blank = {"system": " "}
prune_blank_system_blocks(blank)
assert blank == {}
kept = {"system": "be brief"}
prune_blank_system_blocks(kept)
assert kept == {"system": "be brief"}
def test_prune_blank_system_blocks_keeps_non_text_blocks() -> None:
body = {"system": [{"type": "image", "source": {}}]}
prune_blank_system_blocks(body)
assert body["system"] == [{"type": "image", "source": {}}]
def test_coerce_litellm_payload_handles_dict() -> None:
out = BaseUpstreamProvider._coerce_litellm_payload({"a": 1})
assert out == {"a": 1}