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 @@ -698,7 +698,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 (e.g. LiteLLM) 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``. Mirrors the text branch
# above and ``extract_refusal``.
return last_content.refusal or ""
else:
raise ModelBehaviorError(f"Unexpected content type: {type(last_content)}")

Expand Down
15 changes: 15 additions & 0 deletions tests/test_items_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ def test_extract_last_content_of_refusal_message() -> None:
assert ItemHelpers.extract_last_content(message) == "I cannot do that"


def test_extract_last_content_of_refusal_message_with_null_refusal() -> None:
# Provider gateways (e.g. LiteLLM) and ``model_construct`` streaming paths can surface a
# ``None`` refusal even though the schema types it as ``str``. Since ``extract_last_content``
# is annotated ``-> str``, it must coerce ``None`` to "" rather than return ``None``.
refusal = ResponseOutputRefusal.model_construct(refusal=None, type="refusal")
message = ResponseOutputMessage.model_construct(
id="msg123",
content=[refusal],
role="assistant",
status="completed",
type="message",
)
assert ItemHelpers.extract_last_content(message) == ""


def test_extract_last_content_non_message_returns_empty() -> None:
# Construct some other type of output item, e.g. a tool call, to verify non-message returns "".
tool_call = ResponseFunctionToolCall(
Expand Down