mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
Merge a7659702b6 into 79e63d3e06
This commit is contained in:
commit
1fec22b40d
2 changed files with 64 additions and 3 deletions
|
|
@ -4696,11 +4696,11 @@ class PythonCore(DeprecatedRouterMembers):
|
|||
"""What a fallback continues from once a Responses stream broke after text, raising the primary's error
|
||||
when the request constrains its output or the stream began a tool call, since a continued JSON document or
|
||||
tool call would not parse, and when a content filter cut it, so no other model finishes filtered text."""
|
||||
if source_iterator.started_tool_call:
|
||||
PythonCore._raise_original_mid_stream_error(e)
|
||||
if _mid_stream_error_before_output(e):
|
||||
return None
|
||||
if source_iterator.started_tool_call or isinstance(
|
||||
e.original_exception, litellm_exceptions.ContentPolicyViolationError
|
||||
):
|
||||
if isinstance(e.original_exception, litellm_exceptions.ContentPolicyViolationError):
|
||||
PythonCore._raise_original_mid_stream_error(e)
|
||||
cut_deployment_id: Final = (get_hidden_params(source_iterator) or {}).get("model_id")
|
||||
cut_deployment: Final = (
|
||||
|
|
|
|||
|
|
@ -1643,6 +1643,67 @@ async def test_responses_fallback_surfaces_the_primary_error_once_the_cut_stream
|
|||
router.discard()
|
||||
|
||||
|
||||
def _openai_stream_cut_after_a_tool_call_without_text(failure: Mapping[str, object]) -> httpx.Response:
|
||||
call: Final = {
|
||||
"type": "function_call", "id": "fc_primary", "call_id": "call_primary",
|
||||
"name": "lookup", "arguments": '{"city": "Paris"}', "status": "completed",
|
||||
}
|
||||
return _sse(
|
||||
{"type": "response.created", "sequence_number": 0, "response": {
|
||||
"id": "resp_primary", "object": "response", "created_at": 0, "status": "in_progress",
|
||||
"model": "gpt-6-astra", "output": [],
|
||||
}},
|
||||
{
|
||||
"type": "response.output_item.added", "sequence_number": 1, "output_index": 0,
|
||||
"item": {**call, "arguments": "", "status": "in_progress"},
|
||||
},
|
||||
{
|
||||
"type": "response.function_call_arguments.delta", "sequence_number": 2,
|
||||
"item_id": "fc_primary", "output_index": 0, "delta": call["arguments"],
|
||||
},
|
||||
{"type": "response.output_item.done", "sequence_number": 3, "output_index": 0, "item": call},
|
||||
{**failure, "sequence_number": 4},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"failure",
|
||||
[
|
||||
{"type": "error", "error": {"type": "server_error", "code": "internal_error", "message": "interrupted"}},
|
||||
{"type": "response.failed", "response": {
|
||||
"id": "resp_primary", "object": "response", "created_at": 0, "status": "failed",
|
||||
"model": "gpt-6-astra", "output": [],
|
||||
"error": {"code": "server_error", "message": "interrupted"},
|
||||
}},
|
||||
],
|
||||
ids=["error", "response_failed"],
|
||||
)
|
||||
async def test_responses_fallback_surfaces_the_primary_error_once_the_cut_stream_delivered_a_tool_call_without_text(
|
||||
failure: Mapping[str, object], respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
respx_mock.post("https://api.openai.com/v1/responses").mock(
|
||||
return_value=_openai_stream_cut_after_a_tool_call_without_text(failure)
|
||||
)
|
||||
fallback: Final = respx_mock.post("https://api.anthropic.com/v1/messages").mock(
|
||||
return_value=_anthropic_stream("It is sunny in Paris.")
|
||||
)
|
||||
router: Final = _primary_with_fallback("anthropic/claude-opus-5-5")
|
||||
try:
|
||||
stream: Final = await router.aresponses(
|
||||
model="primary",
|
||||
input="What is the weather in Paris?",
|
||||
stream=True,
|
||||
tools=[{"type": "function", "name": "lookup", "parameters": {"type": "object"}}],
|
||||
)
|
||||
with pytest.raises(litellm.InternalServerError, match="interrupted"):
|
||||
[event async for event in stream]
|
||||
assert fallback.call_count == 0
|
||||
finally:
|
||||
router.discard()
|
||||
|
||||
|
||||
def _openai_stream_cut_after_event(preamble: str, event: Mapping[str, object]) -> httpx.Response:
|
||||
return _sse(
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue