Skip to content

Commit 71df74b

Browse files
committed
Fix mypy issues in nested handoff helper
1 parent 52f0a1e commit 71df74b

File tree

3 files changed

+57
-22
lines changed

3 files changed

+57
-22
lines changed

src/agents/extensions/handoff_filters.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44
from copy import deepcopy
5-
from typing import Any
5+
from typing import Any, cast
66

77
from ..handoffs import HandoffInputData
88
from ..items import (
@@ -88,17 +88,19 @@ def _build_developer_message(transcript: list[TResponseInputItem]) -> TResponseI
8888
transcript_copy = [deepcopy(item) for item in transcript]
8989
if transcript_copy:
9090
summary_lines = [
91-
f"{idx + 1}. {_format_transcript_item(item)}" for idx, item in enumerate(transcript_copy)
91+
f"{idx + 1}. {_format_transcript_item(item)}"
92+
for idx, item in enumerate(transcript_copy)
9293
]
9394
else:
9495
summary_lines = ["(no previous turns recorded)"]
9596

9697
content_lines = [_CONVERSATION_HISTORY_START, *summary_lines, _CONVERSATION_HISTORY_END]
9798
content = "\n".join(content_lines)
98-
return {
99+
developer_message: dict[str, Any] = {
99100
"role": "developer",
100101
"content": content,
101102
}
103+
return cast(TResponseInputItem, developer_message)
102104

103105

104106
def _format_transcript_item(item: TResponseInputItem) -> str:
@@ -190,13 +192,13 @@ def _parse_summary_line(line: str) -> TResponseInputItem | None:
190192
if not role_text:
191193
return None
192194
role, name = _split_role_and_name(role_text)
193-
reconstructed: TResponseInputItem = {"role": role}
195+
reconstructed: dict[str, Any] = {"role": role}
194196
if name:
195197
reconstructed["name"] = name
196198
content = remainder.strip()
197199
if content:
198200
reconstructed["content"] = content
199-
return reconstructed
201+
return cast(TResponseInputItem, reconstructed)
200202

201203

202204
def _split_role_and_name(role_text: str) -> tuple[str, str | None]:

tests/test_agent_runner.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343
from .utils.simple_session import SimpleListSession
4444

4545

46+
def _as_message(item: Any) -> dict[str, Any]:
47+
assert isinstance(item, dict)
48+
role = item.get("role")
49+
assert isinstance(role, str)
50+
assert role in {"assistant", "user", "system", "developer"}
51+
return cast(dict[str, Any], item)
52+
53+
4654
@pytest.mark.asyncio
4755
async def test_simple_first_run():
4856
model = FakeModel()
@@ -293,11 +301,15 @@ async def test_default_handoff_history_nested_and_filters_respected():
293301
result = await Runner.run(agent_2, input="user_message")
294302

295303
assert isinstance(result.input, list)
296-
assert result.input[0]["role"] == "developer"
297-
assert "<CONVERSATION HISTORY>" in result.input[0]["content"]
298-
assert "triage summary" in result.input[0]["content"]
299-
assert result.input[1]["role"] == "user"
300-
assert result.input[1]["content"] == "user_message"
304+
developer = _as_message(result.input[0])
305+
assert developer["role"] == "developer"
306+
developer_content = developer["content"]
307+
assert isinstance(developer_content, str)
308+
assert "<CONVERSATION HISTORY>" in developer_content
309+
assert "triage summary" in developer_content
310+
latest_user = _as_message(result.input[1])
311+
assert latest_user["role"] == "user"
312+
assert latest_user["content"] == "user_message"
301313

302314
passthrough_model = FakeModel()
303315
delegate = Agent(name="delegate", model=passthrough_model)
@@ -348,13 +360,16 @@ async def test_default_handoff_history_accumulates_across_multiple_handoffs():
348360
assert closer_model.first_turn_args is not None
349361
closer_input = closer_model.first_turn_args["input"]
350362
assert isinstance(closer_input, list)
351-
assert closer_input[0]["role"] == "developer"
352-
developer_content = closer_input[0]["content"]
363+
developer = _as_message(closer_input[0])
364+
assert developer["role"] == "developer"
365+
developer_content = developer["content"]
366+
assert isinstance(developer_content, str)
353367
assert developer_content.count("<CONVERSATION HISTORY>") == 1
354368
assert "triage summary" in developer_content
355369
assert "delegate update" in developer_content
356-
assert closer_input[1]["role"] == "user"
357-
assert closer_input[1]["content"] == "user_question"
370+
latest_user = _as_message(closer_input[1])
371+
assert latest_user["role"] == "user"
372+
assert latest_user["content"] == "user_question"
358373

359374

360375
@pytest.mark.asyncio

tests/test_extension_filters.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any, cast
2+
13
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
24
from openai.types.responses.response_reasoning_item import ResponseReasoningItem
35

@@ -96,6 +98,14 @@ def _get_reasoning_output_run_item() -> ReasoningItem:
9698
)
9799

98100

101+
def _as_message(item: TResponseInputItem) -> dict[str, Any]:
102+
assert isinstance(item, dict)
103+
role = item.get("role")
104+
assert isinstance(role, str)
105+
assert role in {"assistant", "user", "system", "developer"}
106+
return cast(dict[str, Any], item)
107+
108+
99109
def test_empty_data():
100110
handoff_input_data = HandoffInputData(
101111
input_history=(),
@@ -242,13 +252,16 @@ def test_nest_handoff_history_wraps_transcript() -> None:
242252
nested = nest_handoff_history(data)
243253

244254
assert isinstance(nested.input_history, tuple)
245-
assert nested.input_history[0]["role"] == "developer"
246-
developer_content = nested.input_history[0]["content"]
255+
developer = _as_message(nested.input_history[0])
256+
assert developer["role"] == "developer"
257+
developer_content = developer["content"]
258+
assert isinstance(developer_content, str)
247259
assert "<CONVERSATION HISTORY>" in developer_content
248260
assert "</CONVERSATION HISTORY>" in developer_content
249261
assert "Assist reply" in developer_content
250-
assert nested.input_history[1]["role"] == "user"
251-
assert nested.input_history[1]["content"] == "Hello"
262+
latest_user = _as_message(nested.input_history[1])
263+
assert latest_user["role"] == "user"
264+
assert latest_user["content"] == "Hello"
252265
assert len(nested.pre_handoff_items) == 0
253266
assert nested.new_items == data.new_items
254267

@@ -265,8 +278,11 @@ def test_nest_handoff_history_handles_missing_user() -> None:
265278

266279
assert isinstance(nested.input_history, tuple)
267280
assert len(nested.input_history) == 1
268-
assert nested.input_history[0]["role"] == "developer"
269-
assert "reasoning" in nested.input_history[0]["content"].lower()
281+
developer = _as_message(nested.input_history[0])
282+
assert developer["role"] == "developer"
283+
developer_content = developer["content"]
284+
assert isinstance(developer_content, str)
285+
assert "reasoning" in developer_content.lower()
270286

271287

272288
def test_nest_handoff_history_appends_existing_history() -> None:
@@ -278,9 +294,10 @@ def test_nest_handoff_history_appends_existing_history() -> None:
278294
)
279295

280296
first_nested = nest_handoff_history(first)
297+
assert isinstance(first_nested.input_history, tuple)
281298
developer_message = first_nested.input_history[0]
282299

283-
follow_up_history = (
300+
follow_up_history: tuple[TResponseInputItem, ...] = (
284301
developer_message,
285302
_get_user_input_item("Another question"),
286303
)
@@ -295,9 +312,10 @@ def test_nest_handoff_history_appends_existing_history() -> None:
295312
second_nested = nest_handoff_history(second)
296313

297314
assert isinstance(second_nested.input_history, tuple)
298-
developer = second_nested.input_history[0]
315+
developer = _as_message(second_nested.input_history[0])
299316
assert developer["role"] == "developer"
300317
content = developer["content"]
318+
assert isinstance(content, str)
301319
assert content.count("<CONVERSATION HISTORY>") == 1
302320
assert content.count("</CONVERSATION HISTORY>") == 1
303321
assert "First reply" in content

0 commit comments

Comments
 (0)