mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
* test(ci): repair MCP Responses and budget fixtures * test(auth): verify delegated budget changes persist
196 lines
8.7 KiB
Python
196 lines
8.7 KiB
Python
import json
|
|
import uuid
|
|
from typing import Final
|
|
|
|
import pytest
|
|
from integration._support.client import Gateway
|
|
from integration._support.wire import Reply, Request, wire_server
|
|
|
|
|
|
@pytest.mark.covers("other.provider_wire.responses_bridge.max_output_tokens_incomplete_maps_to_length")
|
|
def test_chat_over_responses_deployment_returns_length_when_output_tokens_run_out(gateway: Gateway) -> None:
|
|
identity: Final = "responses-incomplete-" + uuid.uuid4().hex
|
|
|
|
def respond(request: Request) -> Reply:
|
|
if request.method == "GET" and request.target == "/v1/models":
|
|
return Reply(body=b'{"object":"list","data":[]}')
|
|
assert request.method == "POST" and request.target == "/responses", request.target
|
|
assert request.headers["authorization"] == "Bearer synthetic-openai-key"
|
|
body: Final = json.loads(request.body)
|
|
assert body["model"] == "gpt-5.3-codex"
|
|
assert body["max_output_tokens"] == 16
|
|
assert body["reasoning"] == {"effort": "high"}
|
|
assert body["input"] == [
|
|
{
|
|
"type": "message",
|
|
"role": "user",
|
|
"content": [{"type": "input_text", "text": f"explain the plan in detail {identity}"}],
|
|
}
|
|
]
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"resp_{identity}",
|
|
"object": "response",
|
|
"created_at": 1789788253,
|
|
"status": "incomplete",
|
|
"incomplete_details": {"reason": "max_output_tokens"},
|
|
"model": "gpt-5.3-codex",
|
|
"output": [{"type": "reasoning", "id": f"rs_{identity}", "summary": []}],
|
|
"usage": {"input_tokens": 12, "output_tokens": 16, "total_tokens": 28},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
|
model: Final = scenario.model(
|
|
model="openai/responses/gpt-5.3-codex", api_base=wire.url, api_key="synthetic-openai-key"
|
|
)
|
|
response: Final = gateway.request(
|
|
"POST",
|
|
"/v1/chat/completions",
|
|
{
|
|
"model": model,
|
|
"messages": [{"role": "user", "content": f"explain the plan in detail {identity}"}],
|
|
"reasoning_effort": "high",
|
|
"max_completion_tokens": 16,
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
body: Final = response.json()
|
|
assert len(tuple(request for request in wire.drain() if request.method == "POST")) == 1
|
|
assert [choice["finish_reason"] for choice in body["choices"]] == ["length"], response.text
|
|
assert body["choices"][0]["message"]["content"] == "", response.text
|
|
assert body["choices"][0]["message"]["role"] == "assistant", response.text
|
|
assert body["usage"]["prompt_tokens"] == 12 and body["usage"]["completion_tokens"] == 16, response.text
|
|
assert body["usage"]["total_tokens"] == 28, response.text
|
|
|
|
|
|
@pytest.mark.covers("other.provider_wire.responses_bridge.sub_minimum_max_tokens_clamped_to_provider_floor")
|
|
def test_messages_over_responses_deployment_with_max_tokens_1_is_clamped_to_16_instead_of_400(gateway: Gateway) -> None:
|
|
identity: Final = "responses-clamp-" + uuid.uuid4().hex
|
|
|
|
def respond(request: Request) -> Reply:
|
|
if request.method == "GET" and request.target == "/v1/models":
|
|
return Reply(body=b'{"object":"list","data":[]}')
|
|
assert request.method == "POST" and request.target == "/responses", request.target
|
|
assert request.headers["authorization"] == "Bearer synthetic-openai-key"
|
|
body: Final = json.loads(request.body)
|
|
assert body["model"] == "gpt-5.4"
|
|
if body["max_output_tokens"] < 16:
|
|
return Reply(
|
|
status=400,
|
|
body=json.dumps(
|
|
{
|
|
"error": {
|
|
"message": "Invalid 'max_output_tokens': integer below minimum value. Expected a value >= 16, but got 1 instead.",
|
|
"type": "invalid_request_error",
|
|
"param": "max_output_tokens",
|
|
"code": "integer_below_min_value",
|
|
}
|
|
}
|
|
).encode(),
|
|
)
|
|
assert body["max_output_tokens"] == 16
|
|
assert body["input"] == [
|
|
{
|
|
"type": "message",
|
|
"role": "user",
|
|
"content": [{"type": "input_text", "text": f"warmup probe {identity}"}],
|
|
}
|
|
]
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"resp_{identity}",
|
|
"object": "response",
|
|
"created_at": 1789788253,
|
|
"status": "completed",
|
|
"model": "gpt-5.4",
|
|
"output": [
|
|
{
|
|
"type": "message",
|
|
"id": f"msg_{identity}",
|
|
"status": "completed",
|
|
"role": "assistant",
|
|
"content": [{"type": "output_text", "text": "ok", "annotations": []}],
|
|
}
|
|
],
|
|
"usage": {"input_tokens": 12, "output_tokens": 1, "total_tokens": 13},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
|
model: Final = scenario.model(
|
|
model="openai/responses/gpt-5.4", api_base=wire.url, api_key="synthetic-openai-key"
|
|
)
|
|
response: Final = gateway.request(
|
|
"POST",
|
|
"/v1/messages",
|
|
{
|
|
"model": model,
|
|
"max_tokens": 1,
|
|
"messages": [{"role": "user", "content": f"warmup probe {identity}"}],
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
body: Final = response.json()
|
|
assert len(tuple(request for request in wire.drain() if request.method == "POST")) == 1
|
|
assert body["role"] == "assistant", response.text
|
|
assert body["content"] == [{"type": "text", "text": "ok"}], response.text
|
|
assert body["stop_reason"] == "end_turn", response.text
|
|
|
|
|
|
@pytest.mark.covers("providers.responses_bridge.sub_minimum_max_tokens_is_raised_to_the_openai_floor")
|
|
def test_messages_over_responses_deployment_with_max_tokens_one_reaches_openai_as_sixteen(gateway: Gateway) -> None:
|
|
identity: Final = "responses-min-tokens-" + uuid.uuid4().hex
|
|
|
|
def respond(request: Request) -> Reply:
|
|
if request.method == "GET" and request.target == "/v1/models":
|
|
return Reply(body=b'{"object":"list","data":[]}')
|
|
assert request.method == "POST" and request.target == "/responses", request.target
|
|
assert request.headers["authorization"] == "Bearer synthetic-openai-key"
|
|
body: Final = json.loads(request.body)
|
|
assert body["model"] == "gpt-5.6-sol"
|
|
assert body["max_output_tokens"] == 16, body
|
|
return Reply(
|
|
body=json.dumps(
|
|
{
|
|
"id": f"resp_{identity}",
|
|
"object": "response",
|
|
"created_at": 1789788253,
|
|
"status": "completed",
|
|
"model": "gpt-5.6-sol",
|
|
"output": [
|
|
{
|
|
"type": "message",
|
|
"id": f"msg_{identity}",
|
|
"status": "completed",
|
|
"role": "assistant",
|
|
"content": [{"type": "output_text", "text": "ok", "annotations": []}],
|
|
}
|
|
],
|
|
"usage": {"input_tokens": 9, "output_tokens": 1, "total_tokens": 10},
|
|
}
|
|
).encode()
|
|
)
|
|
|
|
with wire_server(respond) as wire, gateway.scenario() as scenario:
|
|
model: Final = scenario.model(
|
|
model="openai/responses/gpt-5.6-sol", api_base=wire.url, api_key="synthetic-openai-key"
|
|
)
|
|
response: Final = gateway.request(
|
|
"POST",
|
|
"/v1/messages",
|
|
{
|
|
"model": model,
|
|
"max_tokens": 1,
|
|
"messages": [{"role": "user", "content": f"warmup {identity}"}],
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
body: Final = response.json()
|
|
assert len(tuple(request for request in wire.drain() if request.method == "POST")) == 1
|
|
assert body["content"] == [{"type": "text", "text": "ok"}], response.text
|
|
assert body["usage"]["input_tokens"] == 9 and body["usage"]["output_tokens"] == 1, response.text
|