Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ requires-python = ">=3.9"
license = "MIT"
authors = [{ name = "OpenAI", email = "[email protected]" }]
dependencies = [
"openai>=2.6.1,<3",
"openai>=2.7,<3",
"pydantic>=2.12.3, <3",
"griffe>=1.5.6, <2",
"typing-extensions>=4.12.2, <5",
Expand Down
12 changes: 8 additions & 4 deletions src/agents/models/chatcmpl_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
from ..tool import FunctionTool, Tool
from .fake_id import FAKE_RESPONSES_ID

ResponseInputContentWithAudioParam = Union[ResponseInputContentParam, ResponseInputAudioParam]


class Converter:
@classmethod
Expand Down Expand Up @@ -136,7 +138,9 @@ def message_to_output_items(cls, message: ChatCompletionMessage) -> list[TRespon
)
if message.content:
message_item.content.append(
ResponseOutputText(text=message.content, type="output_text", annotations=[])
ResponseOutputText(
text=message.content, type="output_text", annotations=[], logprobs=[]
)
)
if message.refusal:
message_item.content.append(
Expand Down Expand Up @@ -246,7 +250,7 @@ def maybe_reasoning_message(cls, item: Any) -> ResponseReasoningItemParam | None

@classmethod
def extract_text_content(
cls, content: str | Iterable[ResponseInputContentParam]
cls, content: str | Iterable[ResponseInputContentWithAudioParam]
) -> str | list[ChatCompletionContentPartTextParam]:
all_content = cls.extract_all_content(content)
if isinstance(all_content, str):
Expand All @@ -259,7 +263,7 @@ def extract_text_content(

@classmethod
def extract_all_content(
cls, content: str | Iterable[ResponseInputContentParam]
cls, content: str | Iterable[ResponseInputContentWithAudioParam]
) -> str | list[ChatCompletionContentPartParam]:
if isinstance(content, str):
return content
Expand Down Expand Up @@ -535,7 +539,7 @@ def ensure_assistant_message() -> ChatCompletionAssistantMessageParam:
elif func_output := cls.maybe_function_tool_call_output(item):
flush_assistant_message()
output_content = cast(
Union[str, Iterable[ResponseInputContentParam]], func_output["output"]
Union[str, Iterable[ResponseInputContentWithAudioParam]], func_output["output"]
)
msg: ChatCompletionToolMessageParam = {
"role": "tool",
Expand Down
2 changes: 2 additions & 0 deletions src/agents/models/chatcmpl_stream_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ async def handle_stream(
text="",
type="output_text",
annotations=[],
logprobs=[],
),
)
# Start a new assistant message stream
Expand Down Expand Up @@ -258,6 +259,7 @@ async def handle_stream(
text="",
type="output_text",
annotations=[],
logprobs=[],
),
type="response.content_part.added",
sequence_number=sequence_number.get_and_increment(),
Expand Down
1 change: 1 addition & 0 deletions tests/extensions/memory/test_sqlalchemy_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def _make_message_item(item_id: str, text_value: str) -> TResponseInputItem:
"type": "output_text",
"text": text_value,
"annotations": [],
"logprobs": [],
}
message: ResponseOutputMessageParam = {
"id": item_id,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_agent_as_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ async def test_agent_as_tool_returns_concatenated_text(monkeypatch: pytest.Monke
annotations=[],
text="Hello world",
type="output_text",
logprobs=None,
logprobs=[],
)
],
)
Expand Down Expand Up @@ -304,7 +304,7 @@ async def test_agent_as_tool_custom_output_extractor(monkeypatch: pytest.MonkeyP
annotations=[],
text="Original text",
type="output_text",
logprobs=None,
logprobs=[],
)
],
)
Expand Down
1 change: 1 addition & 0 deletions tests/test_agent_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,7 @@ async def echo_tool(text: str) -> str:
"content": [
{
"annotations": [],
"logprobs": [],
"text": "Summary: Echoed foo and bar",
"type": "output_text",
}
Expand Down
8 changes: 6 additions & 2 deletions tests/test_call_model_input_filter_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ async def test_call_model_input_filter_sync_non_streamed_unit() -> None:
id="1",
type="message",
role="assistant",
content=[ResponseOutputText(text="ok", type="output_text", annotations=[])],
content=[
ResponseOutputText(text="ok", type="output_text", annotations=[], logprobs=[])
],
status="completed",
)
]
Expand Down Expand Up @@ -64,7 +66,9 @@ async def test_call_model_input_filter_async_streamed_unit() -> None:
id="1",
type="message",
role="assistant",
content=[ResponseOutputText(text="ok", type="output_text", annotations=[])],
content=[
ResponseOutputText(text="ok", type="output_text", annotations=[], logprobs=[])
],
status="completed",
)
]
Expand Down
4 changes: 3 additions & 1 deletion tests/test_extension_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def _get_message_output_run_item(content: str) -> MessageOutputItem:
agent=fake_agent(),
raw_item=ResponseOutputMessage(
id="1",
content=[ResponseOutputText(text=content, annotations=[], type="output_text")],
content=[
ResponseOutputText(text=content, annotations=[], type="output_text", logprobs=[])
],
role="assistant",
status="completed",
type="message",
Expand Down
4 changes: 3 additions & 1 deletion tests/test_handoff_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ def message_item(content: str, agent: Agent[Any]) -> MessageOutputItem:
status="completed",
role="assistant",
type="message",
content=[ResponseOutputText(text=content, type="output_text", annotations=[])],
content=[
ResponseOutputText(text=content, type="output_text", annotations=[], logprobs=[])
],
),
)

Expand Down
30 changes: 20 additions & 10 deletions tests/test_items_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,18 @@ def make_message(

def test_extract_last_content_of_text_message() -> None:
# Build a message containing two text segments.
content1 = ResponseOutputText(annotations=[], text="Hello ", type="output_text")
content2 = ResponseOutputText(annotations=[], text="world!", type="output_text")
content1 = ResponseOutputText(annotations=[], text="Hello ", type="output_text", logprobs=[])
content2 = ResponseOutputText(annotations=[], text="world!", type="output_text", logprobs=[])
message = make_message([content1, content2])
# Helpers should yield the last segment's text.
assert ItemHelpers.extract_last_content(message) == "world!"


def test_extract_last_content_of_refusal_message() -> None:
# Build a message whose last content entry is a refusal.
content1 = ResponseOutputText(annotations=[], text="Before refusal", type="output_text")
content1 = ResponseOutputText(
annotations=[], text="Before refusal", type="output_text", logprobs=[]
)
refusal = ResponseOutputRefusal(refusal="I cannot do that", type="refusal")
message = make_message([content1, refusal])
# Helpers should extract the refusal string when last content is a refusal.
Expand All @@ -87,8 +89,8 @@ def test_extract_last_content_non_message_returns_empty() -> None:

def test_extract_last_text_returns_text_only() -> None:
# A message whose last segment is text yields the text.
first_text = ResponseOutputText(annotations=[], text="part1", type="output_text")
second_text = ResponseOutputText(annotations=[], text="part2", type="output_text")
first_text = ResponseOutputText(annotations=[], text="part1", type="output_text", logprobs=[])
second_text = ResponseOutputText(annotations=[], text="part2", type="output_text", logprobs=[])
message = make_message([first_text, second_text])
assert ItemHelpers.extract_last_text(message) == "part2"
# Whereas when last content is a refusal, extract_last_text returns None.
Expand Down Expand Up @@ -116,9 +118,9 @@ def test_input_to_new_input_list_deep_copies_lists() -> None:
def test_text_message_output_concatenates_text_segments() -> None:
# Build a message with both text and refusal segments, only text segments are concatenated.
pieces: list[ResponseOutputText | ResponseOutputRefusal] = []
pieces.append(ResponseOutputText(annotations=[], text="a", type="output_text"))
pieces.append(ResponseOutputText(annotations=[], text="a", type="output_text", logprobs=[]))
pieces.append(ResponseOutputRefusal(refusal="denied", type="refusal"))
pieces.append(ResponseOutputText(annotations=[], text="b", type="output_text"))
pieces.append(ResponseOutputText(annotations=[], text="b", type="output_text", logprobs=[]))
message = make_message(pieces)
# Wrap into MessageOutputItem to feed into text_message_output.
item = MessageOutputItem(agent=Agent(name="test"), raw_item=message)
Expand All @@ -131,8 +133,12 @@ def test_text_message_outputs_across_list_of_runitems() -> None:
that only MessageOutputItem instances contribute any text. The non-message
(ReasoningItem) should be ignored by Helpers.text_message_outputs.
"""
message1 = make_message([ResponseOutputText(annotations=[], text="foo", type="output_text")])
message2 = make_message([ResponseOutputText(annotations=[], text="bar", type="output_text")])
message1 = make_message(
[ResponseOutputText(annotations=[], text="foo", type="output_text", logprobs=[])]
)
message2 = make_message(
[ResponseOutputText(annotations=[], text="bar", type="output_text", logprobs=[])]
)
item1: RunItem = MessageOutputItem(agent=Agent(name="test"), raw_item=message1)
item2: RunItem = MessageOutputItem(agent=Agent(name="test"), raw_item=message2)
# Create a non-message run item of a different type, e.g., a reasoning trace.
Expand Down Expand Up @@ -171,7 +177,9 @@ def test_tool_call_output_item_constructs_function_call_output_dict():

def test_to_input_items_for_message() -> None:
"""An output message should convert into an input dict matching the message's own structure."""
content = ResponseOutputText(annotations=[], text="hello world", type="output_text")
content = ResponseOutputText(
annotations=[], text="hello world", type="output_text", logprobs=[]
)
message = ResponseOutputMessage(
id="m1", content=[content], role="assistant", status="completed", type="message"
)
Expand All @@ -184,6 +192,7 @@ def test_to_input_items_for_message() -> None:
"content": [
{
"annotations": [],
"logprobs": [],
"text": "hello world",
"type": "output_text",
}
Expand Down Expand Up @@ -305,6 +314,7 @@ def test_input_to_new_input_list_copies_the_ones_produced_by_pydantic() -> None:
type="output_text",
text="Hey, what's up?",
annotations=[],
logprobs=[],
)
],
role="assistant",
Expand Down
1 change: 1 addition & 0 deletions tests/test_openai_chatcompletions_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def test_items_to_messages_with_output_message_and_function_call():
text="Part 1",
type="output_text",
annotations=[],
logprobs=[],
)
refusal: ResponseOutputRefusal = ResponseOutputRefusal(
refusal="won't do that",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_text_message(content: str) -> ResponseOutputItem:
id="1",
type="message",
role="assistant",
content=[ResponseOutputText(text=content, type="output_text", annotations=[])],
content=[ResponseOutputText(text=content, type="output_text", annotations=[], logprobs=[])],
status="completed",
)

Expand Down Expand Up @@ -73,6 +73,6 @@ def get_final_output_message(args: str) -> ResponseOutputItem:
id="1",
type="message",
role="assistant",
content=[ResponseOutputText(text=args, type="output_text", annotations=[])],
content=[ResponseOutputText(text=args, type="output_text", annotations=[], logprobs=[])],
status="completed",
)
3 changes: 2 additions & 1 deletion tests/utils/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def test_to_dump_compatible():
type="output_text",
text="Hey, what's up?",
annotations=[],
logprobs=[],
)
].__iter__(),
role="assistant",
Expand All @@ -28,5 +29,5 @@ def test_to_dump_compatible():
result = json.dumps(_to_dump_compatible(input_iter))
assert (
result
== """[{"id": "a75654dc-7492-4d1c-bce0-89e8312fbdd7", "content": [{"type": "output_text", "text": "Hey, what's up?", "annotations": []}], "role": "assistant", "status": "completed", "type": "message"}]""" # noqa: E501
== """[{"id": "a75654dc-7492-4d1c-bce0-89e8312fbdd7", "content": [{"type": "output_text", "text": "Hey, what's up?", "annotations": [], "logprobs": []}], "role": "assistant", "status": "completed", "type": "message"}]""" # noqa: E501
)
20 changes: 15 additions & 5 deletions tests/voice/test_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ async def test_single_agent_workflow(monkeypatch) -> None:
},
{
"id": "1",
"content": [{"annotations": [], "text": "a_message", "type": "output_text"}],
"content": [
{"annotations": [], "logprobs": [], "text": "a_message", "type": "output_text"}
],
"role": "assistant",
"status": "completed",
"type": "message",
Expand All @@ -151,7 +153,9 @@ async def test_single_agent_workflow(monkeypatch) -> None:
},
{
"id": "1",
"content": [{"annotations": [], "text": "done", "type": "output_text"}],
"content": [
{"annotations": [], "logprobs": [], "text": "done", "type": "output_text"}
],
"role": "assistant",
"status": "completed",
"type": "message",
Expand Down Expand Up @@ -179,7 +183,9 @@ async def test_single_agent_workflow(monkeypatch) -> None:
},
{
"id": "1",
"content": [{"annotations": [], "text": "a_message", "type": "output_text"}],
"content": [
{"annotations": [], "logprobs": [], "text": "a_message", "type": "output_text"}
],
"role": "assistant",
"status": "completed",
"type": "message",
Expand All @@ -191,15 +197,19 @@ async def test_single_agent_workflow(monkeypatch) -> None:
},
{
"id": "1",
"content": [{"annotations": [], "text": "done", "type": "output_text"}],
"content": [
{"annotations": [], "logprobs": [], "text": "done", "type": "output_text"}
],
"role": "assistant",
"status": "completed",
"type": "message",
},
{"role": "user", "content": "transcription_2"},
{
"id": "1",
"content": [{"annotations": [], "text": "done_2", "type": "output_text"}],
"content": [
{"annotations": [], "logprobs": [], "text": "done_2", "type": "output_text"}
],
"role": "assistant",
"status": "completed",
"type": "message",
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.