mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
fix(health): pass responses-mode `input` as a list
When ``/health/test_connection`` hits a ``mode: responses`` model, the
handler called ``litellm.aresponses(input=prompt or 'test')`` — a bare
string. OpenAI's public Responses API is tolerant of either shape, but
the ChatGPT/Codex backend enforces a list and returns
``{"detail": "Input must be a list"}``, so the "Test Connection" button
on the models list page failed against ChatGPT OAuth models even though
normal inference worked fine.
Wrap the fallback in a list (``input or [prompt or 'test']``) so both
backends are happy. The calling site in ``main.ahealth_check`` already
forwards ``input=['test from litellm']`` for this mode, so the common
path flows through unchanged.
Regression test mocks ``litellm.aresponses`` and asserts the handler
passes a list for both the ``input=[...]`` and prompt-only cases.
This commit is contained in:
parent
f2653df86b
commit
4ff0de18ef
2 changed files with 49 additions and 2 deletions
|
|
@ -206,8 +206,11 @@ class HealthCheckHelpers:
|
|||
filtered_model_params=_filter_model_params(model_params=model_params),
|
||||
),
|
||||
"responses": lambda: litellm.aresponses(
|
||||
# The ChatGPT/Codex backend rejects string input with
|
||||
# ``{"detail": "Input must be a list"}``; OpenAI's own
|
||||
# Responses API accepts either, so a list works for both.
|
||||
**_filter_model_params(model_params=model_params),
|
||||
input=prompt or "test",
|
||||
input=input or [prompt or "test"],
|
||||
),
|
||||
"ocr": lambda: litellm.aocr(
|
||||
**_filter_model_params(model_params=model_params),
|
||||
|
|
|
|||
|
|
@ -134,4 +134,48 @@ async def test_ahealth_check_failure_masks_raw_request_headers():
|
|||
if "Content-Type" in headers:
|
||||
assert headers["Content-Type"] == "application/json"
|
||||
|
||||
print(f"Masked Authorization header: {headers.get('Authorization', 'NOT FOUND')}")
|
||||
print(f"Masked Authorization header: {headers.get('Authorization', 'NOT FOUND')}")
|
||||
|
||||
|
||||
def test_get_mode_handlers_responses_wraps_input_in_list():
|
||||
"""
|
||||
Regression: the ChatGPT/Codex responses backend rejects string input
|
||||
with ``{"detail": "Input must be a list"}``. OpenAI's public Responses
|
||||
API accepts either shape, so we always pass a list from the health
|
||||
check to keep both backends happy.
|
||||
|
||||
See ``litellm/proxy/health_endpoints/_health_endpoints.py`` —
|
||||
``test_connection`` calls ``ahealth_check`` with
|
||||
``input=["test from litellm"]``; that must flow through unchanged,
|
||||
and the ``prompt``-only fallback must still produce a list.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
handlers = HealthCheckHelpers.get_mode_handlers(
|
||||
model="chatgpt/gpt-5.4-codex",
|
||||
custom_llm_provider="chatgpt",
|
||||
model_params={"model": "chatgpt/gpt-5.4-codex"},
|
||||
prompt="test from litellm",
|
||||
input=["test from litellm"],
|
||||
)
|
||||
assert "responses" in handlers
|
||||
|
||||
with patch(
|
||||
"litellm.aresponses", new=AsyncMock(return_value=MagicMock())
|
||||
) as mock_ar:
|
||||
asyncio.get_event_loop().run_until_complete(handlers["responses"]())
|
||||
assert mock_ar.call_args.kwargs["input"] == ["test from litellm"]
|
||||
|
||||
# Fallback: no ``input`` provided, prompt-only → still a list.
|
||||
prompt_only_handlers = HealthCheckHelpers.get_mode_handlers(
|
||||
model="chatgpt/gpt-5.4-codex",
|
||||
custom_llm_provider="chatgpt",
|
||||
model_params={"model": "chatgpt/gpt-5.4-codex"},
|
||||
prompt="hello",
|
||||
input=None,
|
||||
)
|
||||
with patch(
|
||||
"litellm.aresponses", new=AsyncMock(return_value=MagicMock())
|
||||
) as mock_ar:
|
||||
asyncio.get_event_loop().run_until_complete(prompt_only_handlers["responses"]())
|
||||
assert mock_ar.call_args.kwargs["input"] == ["hello"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue