@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 200a727e94
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 5, 06:00 UTC

[Bugfix] Fix Responses API instructions leaking through previous_response_id (#37727)

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>

Yufeng He committed Apr 13, 2026 at 16:46 UTC 200a727e9490d266e3279281c547272b7877c9f0
2 files changed +73 -2
tests/entrypoints/openai/responses/test_responses_utils.py
+69
@@ -22,6 +22,7 @@ from vllm.entrypoints.openai.responses.utils import (
22 _construct_single_message_from_response_item,
23 _maybe_combine_reasoning_and_tool_call,
24 construct_chat_messages_with_tool_call,
25 + construct_input_messages,
26 convert_tool_responses_to_completions_format,
27 should_continue_final_message,
28 )
@@ -738,3 +739,71 @@ class TestMaybeCombineReasoningAndToolCall:
739 result = _maybe_combine_reasoning_and_tool_call(item, messages)
740
741 assert result is None
742 +
743 +
744 +class TestConstructInputMessagesInstructionsLeak:
745 + """Regression tests for #37697: instructions from a prior response
746 + should NOT leak through previous_response_id."""
747 +
748 + def test_old_instructions_stripped_from_prev_msg(self):
749 + """System message in prev_msg must be dropped so the new request's
750 + instructions are the only system message in the conversation."""
751 + prev = [
752 + {"role": "system", "content": "old instructions"},
753 + {"role": "user", "content": "What is 2+2?"},
754 + {"role": "assistant", "content": "4"},
755 + ]
756 + msgs = construct_input_messages(
757 + request_instructions="new instructions",
758 + request_input="What is 3+3?",
759 + prev_msg=prev,
760 + )
761 + system_msgs = [m for m in msgs if m.get("role") == "system"]
762 + assert len(system_msgs) == 1
763 + assert system_msgs[0]["content"] == "new instructions"
764 +
765 + def test_no_instructions_in_new_request(self):
766 + """If the new request has no instructions, old ones should still
767 + be stripped -- they must not carry over."""
768 + prev = [
769 + {"role": "system", "content": "old instructions"},
770 + {"role": "user", "content": "Hi"},
771 + {"role": "assistant", "content": "Hello"},
772 + ]
773 + msgs = construct_input_messages(
774 + request_instructions=None,
775 + request_input="What is 3+3?",
776 + prev_msg=prev,
777 + )
778 + system_msgs = [m for m in msgs if m.get("role") == "system"]
779 + assert len(system_msgs) == 0
780 +
781 + def test_non_system_messages_preserved(self):
782 + """User/assistant messages from prev_msg must remain intact."""
783 + prev = [
784 + {"role": "system", "content": "old instructions"},
785 + {"role": "user", "content": "Hi"},
786 + {"role": "assistant", "content": "Hello"},
787 + ]
788 + msgs = construct_input_messages(
789 + request_instructions="new instructions",
790 + request_input="Follow up",
791 + prev_msg=prev,
792 + )
793 + roles = [m["role"] for m in msgs]
794 + assert roles == ["system", "user", "assistant", "user"]
795 + assert msgs[0]["content"] == "new instructions"
796 + assert msgs[1]["content"] == "Hi"
797 + assert msgs[2]["content"] == "Hello"
798 + assert msgs[3]["content"] == "Follow up"
799 +
800 + def test_no_prev_msg(self):
801 + """Baseline: when there's no prev_msg, instructions work normally."""
802 + msgs = construct_input_messages(
803 + request_instructions="be helpful",
804 + request_input="hello",
805 + prev_msg=None,
806 + )
807 + assert len(msgs) == 2
808 + assert msgs[0] == {"role": "system", "content": "be helpful"}
809 + assert msgs[1] == {"role": "user", "content": "hello"}
vllm/entrypoints/openai/responses/utils.py
+4 -2
@@ -94,8 +94,10 @@ def construct_input_messages(
94
95 # Prepend the conversation history.
96 if prev_msg is not None:
97 - # Add the previous messages.
98 - messages.extend(prev_msg)
97 + # Filter out system messages from previous conversation -- per the
98 + # OpenAI spec, instructions should NOT carry over across responses.
99 + # The current request's instructions (if any) were already added above.
100 + messages.extend(m for m in prev_msg if m.get("role") != "system")
101 if prev_response_output is not None:
102 # Add the previous output.
103 for output_item in prev_response_output: