From 4dc9d4d059d0fbbf369a075c613810618e0dce61 Mon Sep 17 00:00:00 2001 From: Brennan Butler <64561607+brennanbutler01@users.noreply.github.com> Date: Sun, 20 Sep 2026 09:45:29 +0700 Subject: [PATCH 1/2] fix(streaming): preserve literal double brackets in ChatAdapter output --- dspy/streaming/streaming_listener.py | 6 +++--- tests/streaming/test_streaming.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dspy/streaming/streaming_listener.py b/dspy/streaming/streaming_listener.py index 32e9f9eb57..6ffb400162 100644 --- a/dspy/streaming/streaming_listener.py +++ b/dspy/streaming/streaming_listener.py @@ -327,9 +327,9 @@ def flush(self) -> str: boundary_index = len(last_tokens) return last_tokens[:boundary_index] elif isinstance(settings.adapter, ChatAdapter) or settings.adapter is None: - boundary_index = last_tokens.find("[[") - if boundary_index == -1: - boundary_index = len(last_tokens) + end_identifier = self.adapter_identifiers["ChatAdapter"]["end_identifier"] + boundary = end_identifier.search(last_tokens) + boundary_index = boundary.start() if boundary else len(last_tokens) return last_tokens[:boundary_index] else: raise ValueError( diff --git a/tests/streaming/test_streaming.py b/tests/streaming/test_streaming.py index c71080f571..0e6937484c 100644 --- a/tests/streaming/test_streaming.py +++ b/tests/streaming/test_streaming.py @@ -1621,6 +1621,28 @@ async def multi_field_stream(*args, **kwargs): assert "second response" in second_content +@pytest.mark.parametrize("adapter", [None, dspy.ChatAdapter()]) +@pytest.mark.parametrize( + "content_chunks", + [ + ["Matrix: [[1, 2], [3, 4]]"], + ["Matrix: [", "[", "1, 2], [3, 4]]"], + ["Matrix: [[1, 2], [3, 4]]\n\n[[ ## completed ## ]]"], + ], +) +def test_stream_listener_preserves_literal_double_brackets(adapter, content_chunks): + listener = dspy.streaming.StreamListener(signature_field_name="answer") + chunks = ["[[ ## answer", " ## ]]\n\n", *content_chunks, "\n\n[[ ## completed ## ]]"] + outputs = [] + with dspy.context(adapter=adapter): + for text in chunks: + chunk = ModelResponseStream(choices=[StreamingChoices(delta=Delta(content=text))]) + if output := listener.receive(chunk): + outputs.append(output) + assert "".join(output.chunk for output in outputs) == "Matrix: [[1, 2], [3, 4]]" + assert outputs[-1].is_last_chunk + + def test_stream_listener_could_form_end_identifier_chat_adapter(): listener = dspy.streaming.StreamListener(signature_field_name="answer") From d6e88bbf5187aabb739a48d8d519866d31d1cddc Mon Sep 17 00:00:00 2001 From: Brennan Butler <64561607+brennanbutler01@users.noreply.github.com> Date: Sun, 20 Sep 2026 10:22:02 +0700 Subject: [PATCH 2/2] test(dspy): require inline markers to terminate the stream --- tests/streaming/test_streaming.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/streaming/test_streaming.py b/tests/streaming/test_streaming.py index 0e6937484c..d20e744039 100644 --- a/tests/streaming/test_streaming.py +++ b/tests/streaming/test_streaming.py @@ -1625,14 +1625,14 @@ async def multi_field_stream(*args, **kwargs): @pytest.mark.parametrize( "content_chunks", [ - ["Matrix: [[1, 2], [3, 4]]"], - ["Matrix: [", "[", "1, 2], [3, 4]]"], + ["Matrix: [[1, 2], [3, 4]]", "\n\n[[ ## completed ## ]]"], + ["Matrix: [", "[", "1, 2], [3, 4]]", "\n\n[[ ## completed ## ]]"], ["Matrix: [[1, 2], [3, 4]]\n\n[[ ## completed ## ]]"], ], ) def test_stream_listener_preserves_literal_double_brackets(adapter, content_chunks): listener = dspy.streaming.StreamListener(signature_field_name="answer") - chunks = ["[[ ## answer", " ## ]]\n\n", *content_chunks, "\n\n[[ ## completed ## ]]"] + chunks = ["[[ ## answer", " ## ]]\n\n", *content_chunks] outputs = [] with dspy.context(adapter=adapter): for text in chunks: