diff --git a/tests/entrypoints/openai/responses/conftest.py b/tests/entrypoints/openai/responses/conftest.py index 7ee4629df3..ce17012cdf 100644 --- a/tests/entrypoints/openai/responses/conftest.py +++ b/tests/entrypoints/openai/responses/conftest.py @@ -37,7 +37,6 @@ def pairs_of_event_types() -> dict[str, str]: "response.content_part.done": "response.content_part.added", "response.output_text.done": "response.output_text.delta", "response.reasoning_text.done": "response.reasoning_text.delta", - "response.reasoning_part.done": "response.reasoning_part.added", "response.mcp_call_arguments.done": "response.mcp_call_arguments.delta", "response.mcp_call.completed": "response.mcp_call.in_progress", "response.function_call_arguments.done": "response.function_call_arguments.delta", # noqa: E501 @@ -250,11 +249,8 @@ def _validate_field_consistency(events: list) -> None: active_content_index = None continue - # --- content_part / reasoning_part added: sets content_index - if etype in ( - "response.content_part.added", - "response.reasoning_part.added", - ): + # --- content_part added: sets content_index + if etype == "response.content_part.added": _assert_item_fields(event, etype, active_item_id, active_output_index) content_index = getattr(event, "content_index", None) if active_content_index is None: diff --git a/tests/entrypoints/openai/responses/test_function_call.py b/tests/entrypoints/openai/responses/test_function_call.py index 006ee0f6f2..8fd26c350a 100644 --- a/tests/entrypoints/openai/responses/test_function_call.py +++ b/tests/entrypoints/openai/responses/test_function_call.py @@ -340,12 +340,12 @@ async def test_function_calling_with_streaming_types( # response.created # -> response.in_progress # -> response.output_item.added - # -> response.reasoning_part.added + # -> response.content_part.added # -> response.reasoning_text.delta # .... # -> response.reasoning_text.delta # -> response.reasoning_text.done - # -> response.reasoning_part.done + # -> response.content_part.done # -> response.output_item.done # -> response.output_item.added # -> response.content_part.added @@ -368,12 +368,12 @@ async def test_function_calling_with_streaming_types( # response.created # -> response.in_progress # -> response.output_item.added - # -> response.reasoning_part.added + # -> response.content_part.added # -> response.reasoning_text.delta # .... # -> response.reasoning_text.delta # -> response.reasoning_text.done - # -> response.reasoning_part.done + # -> response.content_part.done # -> response.output_item.done # -> response.output_item.added # -> response.content_part.added @@ -424,7 +424,6 @@ async def test_function_calling_with_streaming_types( "response.output_text.done": "response.output_text.delta", "response.content_part.done": "response.content_part.added", "response.reasoning_text.done": "response.reasoning_text.delta", - "response.reasoning_part.done": "response.reasoning_part.added", "response.function_call_arguments.done": "response.function_call_arguments.delta", # noqa } diff --git a/tests/entrypoints/openai/responses/test_harmony.py b/tests/entrypoints/openai/responses/test_harmony.py index 574f33a8f2..1732382c35 100644 --- a/tests/entrypoints/openai/responses/test_harmony.py +++ b/tests/entrypoints/openai/responses/test_harmony.py @@ -467,10 +467,7 @@ async def test_streaming(client: OpenAI, model_name: str, background: bool): assert event.item_id == current_item_id # Verify content indices - if event.type in [ - "response.content_part.added", - "response.reasoning_part.added", - ]: + if event.type == "response.content_part.added": assert event.content_index == current_content_index + 1 current_content_index = event.content_index elif event.type in [ diff --git a/tests/entrypoints/openai/responses/test_streaming_events.py b/tests/entrypoints/openai/responses/test_streaming_events.py index e1a8bf46f6..21fe5537bb 100644 --- a/tests/entrypoints/openai/responses/test_streaming_events.py +++ b/tests/entrypoints/openai/responses/test_streaming_events.py @@ -1,8 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project -from openai.types.responses import ResponseFunctionWebSearch +import pytest +from openai.types.responses import ResponseFunctionWebSearch, ResponseStreamEvent from openai_harmony import Message, Role +from pydantic import TypeAdapter from vllm.entrypoints.generate.base.protocol import ( DeltaFunctionCall, @@ -14,6 +16,8 @@ from vllm.entrypoints.openai.responses.streaming_events import ( StreamingState, _StateType, emit_browser_tool_events, + emit_reasoning_delta_events, + emit_reasoning_done_events, split_delta, ) @@ -40,6 +44,41 @@ def test_browser_find_uses_responses_action_type(): assert event.item.action.url == "cursor:42" +@pytest.mark.parametrize("harmony", [False, True], ids=["simple", "harmony"]) +def test_reasoning_content_parts_use_sdk_events(harmony): + text = "Let me think." + if harmony: + state = StreamingState() + events = emit_reasoning_delta_events(text, state) + events.extend(emit_reasoning_done_events(text, state)) + else: + processor = SimpleStreamingEventProcessor() + events = _run_through_processor(processor, DeltaMessage(reasoning=text)) + events.extend(processor.close_current()) + + assert [event.type for event in events] == [ + "response.output_item.added", + "response.content_part.added", + "response.reasoning_text.delta", + "response.reasoning_text.done", + "response.content_part.done", + "response.output_item.done", + ] + added, part_added, delta, text_done, part_done, done = events + assert part_added.part.type == part_done.part.type == "reasoning_text" + assert part_added.part.text == "" + assert delta.delta == text_done.text == part_done.part.text == text + assert done.item.content[0].text == text + assert done.item.id == added.item.id + for event in events[1:-1]: + assert event.item_id == added.item.id + assert event.content_index == part_added.content_index + validator = TypeAdapter(ResponseStreamEvent) + for event in events: + assert event.output_index == added.output_index + validator.validate_python(event.model_dump()) + + def _make_tool_call( index: int, name: str | None = None, arguments: str | None = None ) -> DeltaToolCall: diff --git a/vllm/entrypoints/openai/responses/protocol.py b/vllm/entrypoints/openai/responses/protocol.py index 7e286aa782..9c44a04b3a 100644 --- a/vllm/entrypoints/openai/responses/protocol.py +++ b/vllm/entrypoints/openai/responses/protocol.py @@ -42,9 +42,6 @@ from openai.types.responses import ( ResponseInProgressEvent as OpenAIResponseInProgressEvent, ) from openai.types.responses.response import IncompleteDetails, ToolChoice -from openai.types.responses.response_reasoning_item import ( - Content as ResponseReasoningTextContent, -) from openai.types.responses.tool import Tool from openai.types.shared import Metadata, Reasoning from openai_harmony import Message as OpenAIHarmonyMessage @@ -845,50 +842,6 @@ class ResponsesResponse(OpenAIBaseModel): ) -# TODO: this code can be removed once -# https://github.com/openai/openai-python/issues/2634 has been resolved -class ResponseReasoningPartDoneEvent(OpenAIBaseModel): - content_index: int - """The index of the content part that is done.""" - - item_id: str - """The ID of the output item that the content part was added to.""" - - output_index: int - """The index of the output item that the content part was added to.""" - - part: ResponseReasoningTextContent - """The content part that is done.""" - - sequence_number: int - """The sequence number of this event.""" - - type: Literal["response.reasoning_part.done"] - """The type of the event. Always `response.reasoning_part.done`.""" - - -# TODO: this code can be removed once -# https://github.com/openai/openai-python/issues/2634 has been resolved -class ResponseReasoningPartAddedEvent(OpenAIBaseModel): - content_index: int - """The index of the content part that is done.""" - - item_id: str - """The ID of the output item that the content part was added to.""" - - output_index: int - """The index of the output item that the content part was added to.""" - - part: ResponseReasoningTextContent - """The content part that is done.""" - - sequence_number: int - """The sequence number of this event.""" - - type: Literal["response.reasoning_part.added"] - """The type of the event. Always `response.reasoning_part.added`.""" - - # vLLM Streaming Events # Note: we override the response type with the vLLM ResponsesResponse type class ResponseCompletedEvent(OpenAIResponseCompletedEvent): @@ -913,8 +866,6 @@ StreamingResponsesResponse: TypeAlias = ( | ResponseContentPartDoneEvent | ResponseReasoningTextDeltaEvent | ResponseReasoningTextDoneEvent - | ResponseReasoningPartAddedEvent - | ResponseReasoningPartDoneEvent | ResponseCodeInterpreterCallInProgressEvent | ResponseCodeInterpreterCallCodeDeltaEvent | ResponseWebSearchCallInProgressEvent diff --git a/vllm/entrypoints/openai/responses/streaming_events.py b/vllm/entrypoints/openai/responses/streaming_events.py index ae9fc2b25e..b4731dc687 100644 --- a/vllm/entrypoints/openai/responses/streaming_events.py +++ b/vllm/entrypoints/openai/responses/streaming_events.py @@ -70,8 +70,6 @@ from vllm.entrypoints.openai.parser.harmony_utils import ( is_function_recipient, ) from vllm.entrypoints.openai.responses.protocol import ( - ResponseReasoningPartAddedEvent, - ResponseReasoningPartDoneEvent, StreamingResponsesResponse, ) from vllm.entrypoints.openai.responses.utils import ( @@ -220,16 +218,13 @@ def emit_reasoning_delta_events( ) state.current_content_index += 1 events.append( - ResponseReasoningPartAddedEvent( - type="response.reasoning_part.added", + ResponseContentPartAddedEvent( + type="response.content_part.added", sequence_number=-1, output_index=state.current_output_index, item_id=state.current_item_id, content_index=state.current_content_index, - part=ResponseReasoningTextContent( - text="", - type="reasoning_text", - ), + part={"text": "", "type": "reasoning_text"}, ) ) events.append( @@ -457,13 +452,13 @@ def emit_reasoning_done_events( ) ) events.append( - ResponseReasoningPartDoneEvent( - type="response.reasoning_part.done", + ResponseContentPartDoneEvent( + type="response.content_part.done", sequence_number=-1, item_id=state.current_item_id, output_index=state.current_output_index, content_index=state.current_content_index, - part=content, + part=content.model_dump(), ) ) events.append( @@ -969,16 +964,13 @@ def emit_simple_reasoning_open( status="in_progress", ), ), - ResponseReasoningPartAddedEvent( - type="response.reasoning_part.added", + ResponseContentPartAddedEvent( + type="response.content_part.added", sequence_number=-1, output_index=state.output_index, item_id=state.current_item_id, content_index=state.content_index, - part=ResponseReasoningTextContent( - text="", - type="reasoning_text", - ), + part={"text": "", "type": "reasoning_text"}, ), ] @@ -1016,13 +1008,13 @@ def emit_simple_reasoning_done( content_index=state.content_index, text=state.accumulated_text, ), - ResponseReasoningPartDoneEvent( - type="response.reasoning_part.done", + ResponseContentPartDoneEvent( + type="response.content_part.done", sequence_number=-1, item_id=state.current_item_id, output_index=state.output_index, content_index=state.content_index, - part=part, + part=part.model_dump(), ), ResponseOutputItemDoneEvent( type="response.output_item.done",