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: