From dd92067be19885942c94c9cc3bf97f2942e2c71b Mon Sep 17 00:00:00 2001 From: kerry Date: Tue, 22 Sep 2026 21:36:34 +0000 Subject: [PATCH] test(integration): chat over responses deployment returns finish_reason length when output tokens run out (Pylon #7784) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/integration/contracts.json | 3 + .../test_responses_bridge_incomplete.py | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/integration/providers/test_responses_bridge_incomplete.py diff --git a/tests/integration/contracts.json b/tests/integration/contracts.json index d12e3ae4620..e343f850c05 100644 --- a/tests/integration/contracts.json +++ b/tests/integration/contracts.json @@ -45,6 +45,9 @@ "tests/integration/providers/test_request_boundary.py::test_upstream_rejects_corruption_and_accepts_supported_metadata": [ "other.provider_wire.validator_rejects_corruption" ], + "tests/integration/providers/test_responses_bridge_incomplete.py::test_chat_over_responses_deployment_returns_length_when_output_tokens_run_out": [ + "other.provider_wire.responses_bridge.max_output_tokens_incomplete_maps_to_length" + ], "tests/integration/pricing/test_configured_prices.py::test_loaded_router_preserves_cached_defaults_during_real_requests": [ "quota_management.spend_tracking.default_prices.loaded_router_preserves_cached_defaults" ], diff --git a/tests/integration/providers/test_responses_bridge_incomplete.py b/tests/integration/providers/test_responses_bridge_incomplete.py new file mode 100644 index 00000000000..3352ca5775e --- /dev/null +++ b/tests/integration/providers/test_responses_bridge_incomplete.py @@ -0,0 +1,64 @@ +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: + 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(wire.drain()) == 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