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,