From 8e89d7321f4023e45e48d7a8faf79420fe851654 Mon Sep 17 00:00:00 2001 From: wendy Date: Wed, 22 Jul 2026 20:46:32 +0000 Subject: [PATCH] fix(beta): return final root output text --- src/openai/types/beta/beta_response.py | 36 +++++++++++++ tests/lib/responses/test_beta_responses.py | 63 ++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tests/lib/responses/test_beta_responses.py diff --git a/src/openai/types/beta/beta_response.py b/src/openai/types/beta/beta_response.py index eb50bcbcd0..46729e0c13 100644 --- a/src/openai/types/beta/beta_response.py +++ b/src/openai/types/beta/beta_response.py @@ -20,6 +20,7 @@ from .beta_response_output_item import BetaResponseOutputItem from .beta_response_text_config import BetaResponseTextConfig from .beta_tool_choice_function import BetaToolChoiceFunction +from .beta_response_output_message import BetaResponseOutputMessage from .beta_tool_choice_apply_patch import BetaToolChoiceApplyPatch __all__ = [ @@ -614,3 +615,38 @@ class BetaResponse(BaseModel): similar requests and to help OpenAI detect and prevent abuse. [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). """ + + @property + def output_text(self) -> str: + """Convenience property that returns the response's output text. + + For multi-agent responses, this returns text from the last root + `final_answer` message. Otherwise, it aggregates all `output_text` content + blocks from the `output` list. + """ + has_agent_metadata = False + final_root_message: Optional[BetaResponseOutputMessage] = None + + for output in self.output: + if output.type != "message" or output.agent is None: + continue + + has_agent_metadata = True + if output.agent.agent_name == "/root" and output.phase == "final_answer": + final_root_message = output + + if has_agent_metadata and final_root_message is None: + return "" + + texts: List[str] = [] + for output in self.output: + if output.type != "message": + continue + if has_agent_metadata and output is not final_root_message: + continue + + for content in output.content: + if content.type == "output_text": + texts.append(content.text) + + return "".join(texts) diff --git a/tests/lib/responses/test_beta_responses.py b/tests/lib/responses/test_beta_responses.py new file mode 100644 index 0000000000..2f34915c89 --- /dev/null +++ b/tests/lib/responses/test_beta_responses.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import Any + +from openai._models import construct_type_unchecked +from openai.types.beta import BetaResponse + + +def _message( + *texts: str, + agent_name: str | None = None, + phase: str | None = None, +) -> dict[str, Any]: + message: dict[str, Any] = { + "id": "msg_123", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": text, + } + for text in texts + ], + } + if agent_name is not None: + message["agent"] = {"agent_name": agent_name} + if phase is not None: + message["phase"] = phase + return message + + +def _response(*output: dict[str, Any]) -> BetaResponse: + return construct_type_unchecked(type_=BetaResponse, value={"output": list(output)}) + + +def test_output_text_uses_last_root_final_message_for_multi_agent_response() -> None: + response = _response( + _message("old answer", agent_name="/root", phase="final_answer"), + _message("child answer", agent_name="/root/reviewer", phase="final_answer"), + _message("root commentary", agent_name="/root", phase="commentary"), + _message("final ", "answer", agent_name="/root", phase="final_answer"), + ) + + assert response.output_text == "final answer" + + +def test_output_text_is_empty_without_root_final_message_for_multi_agent_response() -> None: + response = _response( + _message("child answer", agent_name="/root/reviewer", phase="final_answer"), + _message("root commentary", agent_name="/root", phase="commentary"), + ) + + assert response.output_text == "" + + +def test_output_text_aggregates_non_multi_agent_response() -> None: + response = _response(_message("first "), _message("second")) + + assert response.output_text == "first second"