@@ -101,6 +101,64 @@ def _make_chat_stream_chunks():
101101 ]
102102
103103
104+ def _make_chat_stream_chunks_with_trailing_content_filter_chunk ():
105+ usage = SimpleNamespace (prompt_tokens = 3 , completion_tokens = 1 , total_tokens = 4 )
106+
107+ return [
108+ SimpleNamespace (
109+ model = "gpt-4o-mini" ,
110+ choices = [
111+ SimpleNamespace (
112+ delta = SimpleNamespace (
113+ role = "assistant" ,
114+ content = "2" ,
115+ function_call = None ,
116+ tool_calls = None ,
117+ ),
118+ finish_reason = None ,
119+ )
120+ ],
121+ usage = None ,
122+ ),
123+ SimpleNamespace (
124+ model = "gpt-4o-mini" ,
125+ choices = [
126+ SimpleNamespace (
127+ delta = SimpleNamespace (
128+ role = None ,
129+ content = None ,
130+ function_call = None ,
131+ tool_calls = None ,
132+ ),
133+ finish_reason = "stop" ,
134+ )
135+ ],
136+ usage = usage ,
137+ ),
138+ SimpleNamespace (
139+ model = "" ,
140+ choices = [
141+ SimpleNamespace (
142+ delta = None ,
143+ finish_reason = None ,
144+ content_filter_offsets = {
145+ "check_offset" : 44 ,
146+ "start_offset" : 44 ,
147+ "end_offset" : 121 ,
148+ },
149+ content_filter_results = {
150+ "hate" : {"filtered" : False , "severity" : "safe" },
151+ "self_harm" : {"filtered" : False , "severity" : "safe" },
152+ "sexual" : {"filtered" : False , "severity" : "safe" },
153+ "violence" : {"filtered" : False , "severity" : "safe" },
154+ },
155+ )
156+ ],
157+ usage = None ,
158+ ),
159+ ]
160+
161+
104162def _make_single_chunk_stream ():
105163 return SimpleNamespace (
106164 model = "gpt-4o-mini" ,
@@ -315,6 +373,41 @@ def test_openai_stream_preserves_original_stream_contract(
315373 }
316374
317375
376+ def test_openai_stream_handles_trailing_azure_content_filter_chunk (
377+ langfuse_memory_client , get_span , json_attr
378+ ):
379+ openai_client = lf_openai .OpenAI (api_key = "test" )
380+ raw_stream = DummyOpenAIStream (
381+ _make_chat_stream_chunks_with_trailing_content_filter_chunk (),
382+ DummySyncResponse (),
383+ )
384+
385+ with patch .object (openai_client .chat .completions , "_post" , return_value = raw_stream ):
386+ stream = openai_client .chat .completions .create (
387+ name = "unit-openai-native-stream-azure-filter" ,
388+ model = "gpt-4o-mini" ,
389+ messages = [{"role" : "user" , "content" : "1 + 1 = ?" }],
390+ temperature = 0 ,
391+ stream = True ,
392+ )
393+
394+ chunks = list (stream )
395+ stream .close ()
396+
397+ assert len (chunks ) == 3
398+
399+ langfuse_memory_client .flush ()
400+ span = get_span ("unit-openai-native-stream-azure-filter" )
401+
402+ assert span .attributes [LangfuseOtelSpanAttributes .OBSERVATION_OUTPUT ] == "2"
403+ assert span .attributes ["langfuse.observation.metadata.finish_reason" ] == "stop"
404+ assert json_attr (span , LangfuseOtelSpanAttributes .OBSERVATION_USAGE_DETAILS ) == {
405+ "prompt_tokens" : 3 ,
406+ "completion_tokens" : 1 ,
407+ "total_tokens" : 4 ,
408+ }
409+
410+
318411def test_openai_stream_break_still_finalizes_generation (
319412 langfuse_memory_client , get_span
320413):
0 commit comments