mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
fix: drop blank system text blocks before litellm dispatch
This commit is contained in:
@@ -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:
|
def coerce_litellm_payload(payload: object) -> dict:
|
||||||
"""Convert a litellm event into a plain 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}
|
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 ""
|
model_suffix = adapt_request(body) if adapt_request else ""
|
||||||
|
|
||||||
# LiteLLM turns Anthropic's server-side web_search tool into the OpenAI
|
# 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.cost_calculation import CostData # noqa: E402
|
||||||
from routstr.payment.models import Architecture, Model, Pricing # noqa: E402
|
from routstr.payment.models import Architecture, Model, Pricing # noqa: E402
|
||||||
from routstr.upstream.base import BaseUpstreamProvider # 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
|
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:
|
def test_coerce_litellm_payload_handles_dict() -> None:
|
||||||
out = BaseUpstreamProvider._coerce_litellm_payload({"a": 1})
|
out = BaseUpstreamProvider._coerce_litellm_payload({"a": 1})
|
||||||
assert out == {"a": 1}
|
assert out == {"a": 1}
|
||||||
|
|||||||
Reference in New Issue
Block a user