@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / f680dc1b39
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 6, 18:15 UTC

[responsesAPI] prioritize content over summary in reasoning item input (#36516)

Signed-off-by: Andrew Xia <axia@meta.com> Signed-off-by: Andrew Xia <mitandrewxia@gmail.com> Signed-off-by: Andrew Xia <axia@fb.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Andrew Xia <axia@fb.com>

Andrew Xia committed Mar 13, 2026 at 18:20 UTC f680dc1b3927c1390abd3a7553e0b15a93683c23
2 files changed +192 -3
tests/entrypoints/test_responses_utils.py
+180
@@ -1,6 +1,8 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +from unittest.mock import patch
5 +
6 import pytest
7 from openai.types.chat import ChatCompletionMessageParam
8 from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
@@ -166,6 +168,184 @@ class TestResponsesUtils:
168 assert formatted_item["content"] == "dongyi"
169
170
171 +class TestReasoningItemContentPriority:
172 + """Tests that content is prioritized over summary for reasoning items."""
173 +
174 + def test_content_preferred_over_summary(self):
175 + """When both content and summary are present, content should win."""
176 + item = ResponseReasoningItem(
177 + id="reasoning_1",
178 + summary=[
179 + Summary(
180 + text="This is a summary",
181 + type="summary_text",
182 + )
183 + ],
184 + type="reasoning",
185 + content=[
186 + Content(
187 + text="This is the actual content",
188 + type="reasoning_text",
189 + )
190 + ],
191 + encrypted_content=None,
192 + status=None,
193 + )
194 + formatted = _construct_single_message_from_response_item(item)
195 + assert formatted["reasoning"] == "This is the actual content"
196 +
197 + def test_content_only(self):
198 + """When only content is present (no summary), content is used."""
199 + item = ResponseReasoningItem(
200 + id="reasoning_2",
201 + summary=[],
202 + type="reasoning",
203 + content=[
204 + Content(
205 + text="Content without summary",
206 + type="reasoning_text",
207 + )
208 + ],
209 + encrypted_content=None,
210 + status=None,
211 + )
212 + formatted = _construct_single_message_from_response_item(item)
213 + assert formatted["reasoning"] == "Content without summary"
214 +
215 + @patch("vllm.entrypoints.openai.responses.utils.logger")
216 + def test_summary_fallback_when_no_content(self, mock_logger):
217 + """When content is absent, summary is used as fallback with warning."""
218 + item = ResponseReasoningItem(
219 + id="reasoning_3",
220 + summary=[
221 + Summary(
222 + text="Fallback summary text",
223 + type="summary_text",
224 + )
225 + ],
226 + type="reasoning",
227 + content=None,
228 + encrypted_content=None,
229 + status=None,
230 + )
231 + formatted = _construct_single_message_from_response_item(item)
232 + assert formatted["reasoning"] == "Fallback summary text"
233 + mock_logger.warning.assert_called_once()
234 + assert (
235 + "summary text as reasoning content" in mock_logger.warning.call_args[0][0]
236 + )
237 +
238 + @patch("vllm.entrypoints.openai.responses.utils.logger")
239 + def test_summary_fallback_when_content_empty(self, mock_logger):
240 + """When content is an empty list, summary is used as fallback."""
241 + item = ResponseReasoningItem(
242 + id="reasoning_4",
243 + summary=[
244 + Summary(
245 + text="Summary when content empty",
246 + type="summary_text",
247 + )
248 + ],
249 + type="reasoning",
250 + content=[],
251 + encrypted_content=None,
252 + status=None,
253 + )
254 + formatted = _construct_single_message_from_response_item(item)
255 + assert formatted["reasoning"] == "Summary when content empty"
256 + mock_logger.warning.assert_called_once()
257 + assert (
258 + "summary text as reasoning content" in mock_logger.warning.call_args[0][0]
259 + )
260 +
261 + def test_neither_content_nor_summary(self):
262 + """When neither content nor summary is present, reasoning is empty."""
263 + item = ResponseReasoningItem(
264 + id="reasoning_5",
265 + summary=[],
266 + type="reasoning",
267 + content=None,
268 + encrypted_content=None,
269 + status=None,
270 + )
271 + formatted = _construct_single_message_from_response_item(item)
272 + assert formatted["reasoning"] == ""
273 +
274 + def test_encrypted_content_raises(self):
275 + """Encrypted content should still raise ValueError."""
276 + item = ResponseReasoningItem(
277 + id="reasoning_6",
278 + summary=[
279 + Summary(
280 + text="Some summary",
281 + type="summary_text",
282 + )
283 + ],
284 + type="reasoning",
285 + content=[
286 + Content(
287 + text="Some content",
288 + type="reasoning_text",
289 + )
290 + ],
291 + encrypted_content="ENCRYPTED",
292 + status=None,
293 + )
294 + with pytest.raises(ValueError):
295 + _construct_single_message_from_response_item(item)
296 +
297 + @patch("vllm.entrypoints.openai.responses.utils.logger")
298 + def test_summary_with_multiple_entries_uses_first(self, mock_logger):
299 + """When multiple summary entries exist, the first one is used."""
300 + item = ResponseReasoningItem(
301 + id="reasoning_7",
302 + summary=[
303 + Summary(
304 + text="First summary",
305 + type="summary_text",
306 + ),
307 + Summary(
308 + text="Second summary",
309 + type="summary_text",
310 + ),
311 + ],
312 + type="reasoning",
313 + content=None,
314 + encrypted_content=None,
315 + status=None,
316 + )
317 + formatted = _construct_single_message_from_response_item(item)
318 + assert formatted["reasoning"] == "First summary"
319 + mock_logger.warning.assert_called_once()
320 + assert (
321 + "summary text as reasoning content" in mock_logger.warning.call_args[0][0]
322 + )
323 +
324 + @patch("vllm.entrypoints.openai.responses.utils.logger")
325 + def test_no_warning_when_content_used(self, mock_logger):
326 + """No warning should be emitted when content is available."""
327 + item = ResponseReasoningItem(
328 + id="reasoning_8",
329 + summary=[
330 + Summary(
331 + text="Summary text",
332 + type="summary_text",
333 + )
334 + ],
335 + type="reasoning",
336 + content=[
337 + Content(
338 + text="Content text",
339 + type="reasoning_text",
340 + )
341 + ],
342 + encrypted_content=None,
343 + status=None,
344 + )
345 + _construct_single_message_from_response_item(item)
346 + mock_logger.warning.assert_not_called()
347 +
348 +
349 class TestShouldContinueFinalMessage:
350 """Tests for should_continue_final_message function.
351
vllm/entrypoints/openai/responses/utils.py
+12 -3
@@ -24,6 +24,9 @@ from vllm import envs
24 from vllm.entrypoints.constants import MCP_PREFIX
25 from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionMessageParam
26 from vllm.entrypoints.openai.responses.protocol import ResponseInputOutputItem
27 +from vllm.logger import init_logger
28 +
29 +logger = init_logger(__name__)
30
31
32 def should_continue_final_message(
@@ -191,10 +194,16 @@ def _construct_single_message_from_response_item(
194 reasoning_content = ""
195 if item.encrypted_content:
196 raise ValueError("Encrypted content is not supported.")
194 - if len(item.summary) == 1:
195 - reasoning_content = item.summary[0].text
196 - elif item.content and len(item.content) == 1:
197 + elif item.content and len(item.content) >= 1:
198 reasoning_content = item.content[0].text
199 + elif len(item.summary) >= 1:
200 + reasoning_content = item.summary[0].text
201 + logger.warning(
202 + "Using summary text as reasoning content for item %s. "
203 + "Please use content instead of summary for "
204 + "reasoning items.",
205 + item.id,
206 + )
207 return {
208 "role": "assistant",
209 "reasoning": reasoning_content,