mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(proxy): preserve chatgpt responses streaming (#2012)
## Description Closes #1956 Keep ChatGPT OAuth `/v1/responses` requests streaming when CCR retrieve tools are present. The buffered `stream:false` conversion is still used for regular OpenAI Responses CCR requests, but ChatGPT Codex routing now bypasses that conversion so the upstream receives the streaming request shape it expects. ## Type of Change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [ ] Refactoring - [ ] Performance improvement - [ ] Test update - [ ] Other ## Changes Made - Extracted the OpenAI Responses CCR stream-buffering decision into a small helper. - Excluded ChatGPT OAuth/Codex-routed requests from the buffered `stream:false` path. - Added tests proving regular OpenAI CCR still buffers while ChatGPT OAuth CCR remains streaming. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Formatting verified (`ruff format --check`) - [ ] Manual testing performed ### Test Output ```text $ python3 -m pytest tests/test_proxy_openai_responses_stream_ccr.py -q collected 3 items tests/test_proxy_openai_responses_stream_ccr.py ... [100%] ============================== 3 passed in 0.59s =============================== $ .venv/bin/ruff check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py All checks passed! $ .venv/bin/ruff format --check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py 2 files already formatted ``` ## Test verification (RED -> GREEN) RED, with the ChatGPT OAuth guard temporarily removed from the buffering decision: ```text tests/test_proxy_openai_responses_stream_ccr.py .F. [100%] FAILED tests/test_proxy_openai_responses_stream_ccr.py::test_responses_ccr_keeps_chatgpt_oauth_requests_streaming E AssertionError: assert not True E + where True = _should_buffer(tools=[{'type': 'function', 'name': 'headroom_retrieve'}], is_chatgpt_auth=True) ``` GREEN, with this patch applied: ```text tests/test_proxy_openai_responses_stream_ccr.py ... [100%] ============================== 3 passed in 0.59s =============================== ``` ## Real Behavior Proof - Environment: Linux, Python 3.12.3, pytest 9.1.1, ruff 0.14.14. - Exact command / steps: Removed the `not is_chatgpt_auth` guard from the CCR buffering decision, ran the targeted tests, restored the guard, and reran the tests plus targeted ruff checks. - Observed result: The ChatGPT OAuth streaming regression test fails without the guard and passes with the guard, while regular OpenAI CCR buffering remains covered. - Not tested: Full `uv run pytest`, full-project `uv run ruff check .`, full-project `uv run ruff format --check .`, and `uv run mypy headroom` were not run locally; `uv run --extra dev ruff` attempted to build the Rust extension in this worktree, so targeted checks used the existing `.venv/bin/ruff`. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have added tests that prove my fix is effective - [x] New and existing targeted tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules ## Screenshots (if applicable) N/A ## Additional Notes The existing buffered CCR path is preserved for non-ChatGPT OpenAI Responses requests.
This commit is contained in:
parent
70b98b6485
commit
a617455f02
2 changed files with 58 additions and 4 deletions
|
|
@ -648,6 +648,23 @@ def _has_headroom_retrieve_tool_responses(tools: Any) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _should_buffer_openai_responses_stream_ccr(
|
||||
*,
|
||||
stream: bool,
|
||||
ccr_response_handler_enabled: bool,
|
||||
tools: Any,
|
||||
is_chatgpt_auth: bool,
|
||||
) -> bool:
|
||||
"""Return whether streaming Responses CCR should use buffered JSON mode."""
|
||||
|
||||
return bool(
|
||||
stream
|
||||
and ccr_response_handler_enabled
|
||||
and not is_chatgpt_auth
|
||||
and _has_headroom_retrieve_tool_responses(tools)
|
||||
)
|
||||
|
||||
|
||||
def _responses_input_to_items(input_data: Any) -> list[dict[str, Any]]:
|
||||
"""Normalize a Responses ``input`` field into an item list for CCR continuation.
|
||||
|
||||
|
|
@ -4206,10 +4223,11 @@ class OpenAIHandlerMixin:
|
|||
_ccr_response_handler_enabled = bool(
|
||||
_ccr_response_handler and getattr(_ccr_handler_config, "enabled", True)
|
||||
)
|
||||
buffered_stream_ccr = bool(
|
||||
stream
|
||||
and _ccr_response_handler_enabled
|
||||
and _has_headroom_retrieve_tool_responses(body.get("tools"))
|
||||
buffered_stream_ccr = _should_buffer_openai_responses_stream_ccr(
|
||||
stream=stream,
|
||||
ccr_response_handler_enabled=_ccr_response_handler_enabled,
|
||||
tools=body.get("tools"),
|
||||
is_chatgpt_auth=is_chatgpt_auth,
|
||||
)
|
||||
if buffered_stream_ccr:
|
||||
if body.get("stream") is not False:
|
||||
|
|
|
|||
36
tests/test_proxy_openai_responses_stream_ccr.py
Normal file
36
tests/test_proxy_openai_responses_stream_ccr.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from headroom.ccr import CCR_TOOL_NAME
|
||||
from headroom.proxy.handlers.openai import _should_buffer_openai_responses_stream_ccr
|
||||
|
||||
_TOOL_TYPE_FUNCTION = "function"
|
||||
_UNRELATED_TOOL_NAME = "unrelated_tool"
|
||||
|
||||
|
||||
def _ccr_tool() -> dict[str, str]:
|
||||
return {"type": _TOOL_TYPE_FUNCTION, "name": CCR_TOOL_NAME}
|
||||
|
||||
|
||||
def _unrelated_tool() -> dict[str, str]:
|
||||
return {"type": _TOOL_TYPE_FUNCTION, "name": _UNRELATED_TOOL_NAME}
|
||||
|
||||
|
||||
def _should_buffer(*, tools: list[dict[str, str]], is_chatgpt_auth: bool) -> bool:
|
||||
return _should_buffer_openai_responses_stream_ccr(
|
||||
stream=True,
|
||||
ccr_response_handler_enabled=True,
|
||||
tools=tools,
|
||||
is_chatgpt_auth=is_chatgpt_auth,
|
||||
)
|
||||
|
||||
|
||||
def test_responses_ccr_buffers_streaming_openai_requests() -> None:
|
||||
assert _should_buffer(tools=[_ccr_tool()], is_chatgpt_auth=False)
|
||||
|
||||
|
||||
def test_responses_ccr_keeps_chatgpt_oauth_requests_streaming() -> None:
|
||||
assert not _should_buffer(tools=[_ccr_tool()], is_chatgpt_auth=True)
|
||||
|
||||
|
||||
def test_responses_ccr_ignores_requests_without_retrieve_tool() -> None:
|
||||
assert not _should_buffer(tools=[_unrelated_tool()], is_chatgpt_auth=False)
|
||||
Loading…
Add table
Add a link
Reference in a new issue