Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,12 @@ def extract_last_content(cls, message: TResponseOutputItem) -> str:
# ``extract_text`` below.
return last_content.text or ""
elif isinstance(last_content, ResponseOutputRefusal):
return last_content.refusal
# ``last_content.refusal`` is typed as ``str`` per the Responses API
# schema, but provider gateways and ``model_construct`` paths during
# streaming have been observed surfacing ``None``. Coerce so callers
# relying on the ``-> str`` return type don't see a ``None``. Same
# rationale as the text branch above.
return last_content.refusal or ""
else:
raise ModelBehaviorError(f"Unexpected content type: {type(last_content)}")

Expand Down
27 changes: 27 additions & 0 deletions tests/utils/test_pretty_print_and_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,33 @@ def test_extract_last_content_returns_text_normally():
assert ItemHelpers.extract_last_content(msg) == "hello"


def _make_refusal_message(refusal: str | None) -> ResponseOutputMessage:
from openai.types.responses import ResponseOutputRefusal

return ResponseOutputMessage.model_construct(
id="msg_1",
role="assistant",
status="completed",
content=[ResponseOutputRefusal.model_construct(type="refusal", refusal=refusal)],
)


def test_extract_last_content_returns_empty_string_for_none_refusal():
"""extract_last_content is declared `-> str` and must not return None even if
the underlying ResponseOutputRefusal.refusal is None (observed via provider
gateways and ``model_construct`` paths, matching the text branch fix in
items.py:687-691)."""
msg = _make_refusal_message(None)
result = ItemHelpers.extract_last_content(msg)
assert isinstance(result, str)
assert result == ""


def test_extract_last_content_returns_refusal_normally():
msg = _make_refusal_message("I cannot do that")
assert ItemHelpers.extract_last_content(msg) == "I cannot do that"


def _make_run_error_details(n_input: int = 0, n_output: int = 0) -> RunErrorDetails:
return RunErrorDetails(
input="hi",
Expand Down
Loading