Summary
When a model refuses a request during a streaming chat completion, the refusal text arrives via delta.refusal on each chunk. The wrapper's _postprocess_streaming_results (lines 288–356 of oai.py) does not accumulate this field, so the assembled span output contains no refusal text. The message dict in the output only includes role, content, and tool_calls.
Non-streaming calls correctly capture refusals because the full choices dict is logged directly (line 204), which includes message.refusal.
What is missing
ChoiceDelta.refusal is a documented Optional[str] field in the OpenAI Python SDK. When a model refuses a request in streaming mode:
delta.content is typically None
delta.refusal contains the refusal text chunks
- The wrapper only checks
delta.get("content") (line 312) and delta.get("tool_calls") (line 315), never delta.get("refusal")
Result: the span output shows "content": null with no indication the model refused. Users debugging why a streaming call produced no output have no visibility into the refusal reason from the span.
Braintrust docs status
not_found — The OpenAI integration docs do not mention refusal capture.
Upstream sources
- OpenAI Python SDK type:
ChoiceDelta.refusal: Optional[str] — "The refusal message generated by the model." (source)
- Non-streaming equivalent:
ChatCompletionMessage.refusal: Optional[str]
Local files inspected
py/src/braintrust/oai.py:
_postprocess_streaming_results (lines 288–356): processes delta.get("content") at line 312 and delta.get("tool_calls") at line 315, but never reads delta.get("refusal")
- Assembled output (lines 343–356):
message dict has role, content, tool_calls — no refusal key
- Non-streaming path (line 204): logs full
choices which includes message.refusal — correct
py/src/braintrust/wrappers/test_openai.py: no test for refusal in streaming mode
Summary
When a model refuses a request during a streaming chat completion, the refusal text arrives via
delta.refusalon each chunk. The wrapper's_postprocess_streaming_results(lines 288–356 ofoai.py) does not accumulate this field, so the assembled span output contains no refusal text. Themessagedict in the output only includesrole,content, andtool_calls.Non-streaming calls correctly capture refusals because the full
choicesdict is logged directly (line 204), which includesmessage.refusal.What is missing
ChoiceDelta.refusalis a documentedOptional[str]field in the OpenAI Python SDK. When a model refuses a request in streaming mode:delta.contentis typicallyNonedelta.refusalcontains the refusal text chunksdelta.get("content")(line 312) anddelta.get("tool_calls")(line 315), neverdelta.get("refusal")Result: the span output shows
"content": nullwith no indication the model refused. Users debugging why a streaming call produced no output have no visibility into the refusal reason from the span.Braintrust docs status
not_found — The OpenAI integration docs do not mention refusal capture.
Upstream sources
ChoiceDelta.refusal: Optional[str]— "The refusal message generated by the model." (source)ChatCompletionMessage.refusal: Optional[str]Local files inspected
py/src/braintrust/oai.py:_postprocess_streaming_results(lines 288–356): processesdelta.get("content")at line 312 anddelta.get("tool_calls")at line 315, but never readsdelta.get("refusal")messagedict hasrole,content,tool_calls— norefusalkeychoiceswhich includesmessage.refusal— correctpy/src/braintrust/wrappers/test_openai.py: no test for refusal in streaming mode