fix(translation): openai-dialect responses without a wire body error loudly (critic-openai M3)

serialize_response fell through to the anthropic assembly when an
openai-dialect ChatResponse carried no wire body, silently serving a
wrong-shaped body if a future openai-dialect parser forgets to set
ChatResponse.wire (azure/azure_ai now extend exactly this path). The
function returns Body | TranslationError; both pipeline call sites lift
the error into the Result, and a regression test pins the loud error.
This commit is contained in:
mateo-berri
2026-06-12 06:23:48 +00:00
parent 3f5491448e
commit 1b6d8745e1
3 changed files with 52 additions and 7 deletions
+15 -5
View File
@@ -180,11 +180,20 @@ def translate_chat_response(
)
)
dialect = response_dialect(provider)
return parser(raw_response, request).map(
lambda response: serialize_response(response, deps, dialect)
return parser(raw_response, request).bind(
lambda response: _serialized_body(response, deps, dialect)
)
def _serialized_body(
response: ChatResponse, deps: TranslationDeps, dialect: ResponseDialect
) -> TranslateResult:
body = serialize_response(response, deps, dialect)
if isinstance(body, TranslationError):
return Error(body)
return Ok(body)
@dataclass(frozen=True)
class PreparedRequest:
"""A request that passed the fail-closed translation; from here on the
@@ -268,9 +277,10 @@ async def send_prepared(
)
match parser(response.body, prepared.request):
case Result(tag="ok", ok=chat_response):
return Ok(
serialize_response(chat_response, deps, response_dialect(provider))
)
body = serialize_response(chat_response, deps, response_dialect(provider))
if isinstance(body, TranslationError):
return Error(ExecuteError.of_translation(body))
return Ok(body)
case Result(error=response_err):
return Error(ExecuteError.of_translation(response_err))
@@ -29,6 +29,7 @@ from expression import Option
from expression.collections import Block
from ...deps import TranslationDeps
from ...errors import BoundaryError, TranslationError
from ...ir import Body, ChatResponse, ContentBlock, PlainJson, ResponseUsage
ResponseDialect = Literal["anthropic", "bedrock_converse", "openai", "gemini"]
@@ -38,13 +39,27 @@ def serialize_response(
response: ChatResponse,
deps: TranslationDeps,
dialect: ResponseDialect = "anthropic",
) -> Body:
) -> Body | TranslationError:
if dialect == "openai":
match response.wire:
case Option(tag="some", some=blob) if isinstance(blob.value, dict):
return blob.value
case _:
pass # no wire body: fall through to the anthropic assembly
# The openai dialect has no assembly of its own: the provider
# parser MUST ride the normalized body on ChatResponse.wire.
# A registered openai-dialect parser that does not is a
# wiring bug; serving the anthropic assembly here would put a
# wrong-shaped body on the wire silently (critic-openai M3).
return TranslationError.of_boundary(
BoundaryError.of(
Block.of_seq(
[
"openai-dialect response carries no wire body;"
" the provider parser must set ChatResponse.wire"
]
)
)
)
if dialect == "gemini":
return _gemini_body(response)
text = "".join(block.text.text for block in response.content if block.tag == "text")
@@ -345,3 +345,23 @@ def test_unreachable_response_shape_is_a_typed_error(name: str) -> None:
result = parse_response(copy.deepcopy(raw), parsed.ok)
assert result.is_error(), f"{name} unexpectedly parsed"
assert reason_fragment in result.error.summary, result.error.summary
def test_openai_dialect_without_wire_body_is_a_loud_error() -> None:
"""An openai-dialect response whose parser failed to set
ChatResponse.wire must be a typed error, never a silently served
anthropic-shaped body (critic-openai M3)."""
import dataclasses
from expression import Nothing
from litellm.translation.errors import TranslationError
parsed = parse_request(copy.deepcopy(_REQUEST))
assert parsed.is_ok()
response = parse_response(copy.deepcopy(_RESPONSES["text"]), parsed.ok)
assert response.is_ok()
wireless = dataclasses.replace(response.ok, wire=Nothing)
body = serialize_response(wireless, build_translation_deps(), "openai")
assert isinstance(body, TranslationError)
assert "wire body" in body.summary