@@ -311,6 +311,69 @@ def test_chat_completion_exports_generation_span(
311311 }
312312
313313
314+ def test_chat_completion_with_none_choices_does_not_crash (
315+ langfuse_memory_client , get_span , json_attr
316+ ):
317+ openai_client = lf_openai .OpenAI (api_key = "test" )
318+ response = SimpleNamespace (
319+ model = "gpt-4o-mini" ,
320+ choices = None ,
321+ usage = SimpleNamespace (prompt_tokens = 3 , completion_tokens = 1 , total_tokens = 4 ),
322+ )
323+
324+ with patch .object (openai_client .chat .completions , "_post" , return_value = response ):
325+ result = openai_client .chat .completions .create (
326+ name = "unit-openai-chat-none-choices" ,
327+ model = "gpt-4o-mini" ,
328+ messages = [{"role" : "user" , "content" : "1 + 1 = ?" }],
329+ )
330+
331+ assert result is response
332+
333+ langfuse_memory_client .flush ()
334+ span = get_span ("unit-openai-chat-none-choices" )
335+
336+ assert LangfuseOtelSpanAttributes .OBSERVATION_LEVEL not in span .attributes
337+ assert (
338+ span .attributes [LangfuseOtelSpanAttributes .OBSERVATION_MODEL ] == "gpt-4o-mini"
339+ )
340+ assert json_attr (span , LangfuseOtelSpanAttributes .OBSERVATION_USAGE_DETAILS ) == {
341+ "prompt_tokens" : 3 ,
342+ "completion_tokens" : 1 ,
343+ "total_tokens" : 4 ,
344+ }
345+
346+
347+ def test_openai_stream_with_none_choices_chunk_does_not_crash (
348+ langfuse_memory_client , get_span
349+ ):
350+ openai_client = lf_openai .OpenAI (api_key = "test" )
351+ chunks_with_none_choices = [
352+ SimpleNamespace (model = "gpt-4o-mini" , choices = None , usage = None ),
353+ * _make_chat_stream_chunks (),
354+ ]
355+ raw_stream = DummyOpenAIStream (chunks_with_none_choices , DummySyncResponse ())
356+
357+ with patch .object (openai_client .chat .completions , "_post" , return_value = raw_stream ):
358+ stream = openai_client .chat .completions .create (
359+ name = "unit-openai-stream-none-choices" ,
360+ model = "gpt-4o-mini" ,
361+ messages = [{"role" : "user" , "content" : "1 + 1 = ?" }],
362+ stream = True ,
363+ )
364+
365+ chunks = list (stream )
366+ stream .close ()
367+
368+ assert len (chunks ) == 3
369+
370+ langfuse_memory_client .flush ()
371+ span = get_span ("unit-openai-stream-none-choices" )
372+
373+ assert span .attributes [LangfuseOtelSpanAttributes .OBSERVATION_OUTPUT ] == "2"
374+ assert span .attributes ["langfuse.observation.metadata.finish_reason" ] == "stop"
375+
376+
314377def test_streaming_chat_completion_preserves_tool_calls_after_content ():
315378 model , completion , usage , metadata = (
316379 lf_openai_module ._extract_streamed_openai_response (
0 commit comments