RunState round trip drops alias-only fields: `"async": null` on a function call is serialized as `async_` and breaks `get_interruptions()` on restore
Maintainer thường phản hồi trong vòng 1 ngày
@iveteamorim đang làm issue này rồi.
Từ ngày 12/10/2026.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 58/100
Hướng nghiên cứu
Read the serialization and restore paths in agents/run_state.py: _serialize_raw_item_value, the ResponseFunctionToolCall reconstruction sites, and _copy_pydantic_value. Add a round-trip regression test for a function call with "async": null and run the relevant RunState serialization tests. Done means a serialized paused state restores and get_interruptions() succeeds with the alias-only field preserved.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Please read this first
- Have you read the docs? Yes (human-in-the-loop /
RunStateserialization). - Have you searched for related issues? Yes. #5162 (
created_bysurvivingmodel_dump(exclude_unset=True)and breaking replay) is the same class of problem at a different boundary; I found no issue about alias-only fields inRunStateround trips.
Describe the bug
RunState.to_string() / to_json() serialize raw output items with model_dump(exclude_unset=True) but without by_alias=True. For a field that only exists under an alias, the JSON ends up keyed by the Python attribute name, and from_string() / from_json() cannot read it back.
Concrete case: the Responses API now has an async flag on function calls, which openai models as ResponseFunctionToolCall.async_ (alias="async"). When a provider sends "async": null on a function call:
openaiparses it intoasync_ = Noneand marks the field as set.- The paused
RunStateis serialized as"async_": null(10 places in my dump, includingcurrent_step.data.interruptions[0].raw_item). - On restore,
ResponseFunctionToolCall(**data)only knows the aliasasync, soasync_lands inmodel_extra. get_interruptions()→_copy_tool_approval_raw_item→_copy_pydantic_valueraisesTypeError("Colliding key in tool approval model extras")because the extra key collides with a declared field, andget_interruptions()re-raises it asUserErrorwith the cause removed.
So a run can pause successfully and then be impossible to resume from its own serialized state. Pausing is the common HITL path, so a provider that includes this (or any future alias-only) field makes every resume fail deterministically.
Debug information
- Agents SDK version:
v0.22.2(also reproduced onv0.23.1, the latest release) - Related library versions:
openai3.13.0 (also 3.29.0), pydantic 2.13 - Python version: 3.12.14
- Operating system: Linux (WSL2)
- Model and model provider: any Responses-compatible provider that emits the
asyncflag on function calls; the repro below uses a customModelso no provider or API key is needed - Does the issue reproduce with the latest Agents SDK release? Yes, 0.23.1
- Does the issue occur consistently or intermittently? Consistently
serialized contains 'async_': True
Traceback (most recent call last):
File "/tmp/sdk-issue/repro.py", line 50, in <module>
asyncio.run(main())
File "/home/yangshuo/.local/share/micromamba/envs/storagent/lib/python3.12/asyncio/runners.py", line 195, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/home/yangshuo/.local/share/micromamba/envs/storagent/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/yangshuo/.local/share/micromamba/envs/storagent/lib/python3.12/asyncio/base_events.py", line 691, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "/tmp/sdk-issue/repro.py", line 46, in main
restored.get_interruptions() # raises UserError
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/yangshuo/.local/share/micromamba/envs/storagent/lib/python3.12/site-packages/agents/run_state.py", line 1056, in get_interruptions
_raise_data_redacted_error(copy_error)
File "/home/yangshuo/.local/share/micromamba/envs/storagent/lib/python3.12/site-packages/agents/exceptions.py", line 410, in _raise_data_redacted_error
raise error from None
agents.exceptions.UserError: Cannot safely copy pending tool approvals. Ensure each interruption uses a supported tool call or contains only JSON-compatible mapping data.
Repro steps
"""RunState round-trip loses alias-only fields: `"async": null` -> `"async_"` -> UserError."""
import asyncio
from typing import Any
from openai.types.responses import ResponseFunctionToolCall
from agents import Agent, ModelResponse, RunConfig, Runner, RunState, function_tool
from agents.models.interface import Model
from agents.usage import Usage
class OneToolCallModel(Model):
async def get_response(self, *args: Any, **kwargs: Any) -> ModelResponse:
# What a Responses-compatible relay sends when it includes the new `async` flag.
call = ResponseFunctionToolCall.model_validate(
{
"id": "fc_1",
"call_id": "call_1",
"type": "function_call",
"name": "needs_ok",
"arguments": "{}",
"async": None,
}
)
return ModelResponse(output=[call], usage=Usage(), response_id="resp_1")
async def stream_response(self, *args: Any, **kwargs: Any):
raise NotImplementedError
yield
@function_tool(needs_approval=True)
async def needs_ok() -> str:
return "ok"
async def main() -> None:
agent = Agent(name="a", model=OneToolCallModel(), tools=[needs_ok])
result = await Runner.run(agent, "go", run_config=RunConfig(tracing_disabled=True))
assert result.interruptions, "expected a pending approval"
serialized = result.to_state().to_string()
print("serialized contains 'async_':", '"async_"' in serialized)
restored = await RunState.from_string(agent, serialized)
restored.get_interruptions() # raises UserError
print("restored OK")
asyncio.run(main())
Output on 0.22.2 and 0.23.1:
serialized contains 'async_': True
...
agents.exceptions.UserError: Cannot safely copy pending tool approvals. Ensure each interruption uses a supported tool call or contains only JSON-compatible mapping data.
Relevant code (0.22.2 line numbers; 0.23.1 is the same logic):
agents/run_state.py:2488_serialize_raw_item_value:model_dump(exclude_unset=True)withoutby_aliasagents/run_state.py:3170/:3225: restore viaResponseFunctionToolCall(**data), which only accepts the aliasagents/run_state.py:439_copy_pydantic_value:Colliding key in tool approval model extrasagents/run_state.py:1046-1056get_interruptions: wraps it intoUserErrorwithraise ... from None, so the originalTypeErroris not visible to callers
Expected behavior
A RunState produced by to_string() / to_json() restores and resumes unchanged. Either serialize raw items with by_alias=True (so the JSON matches the wire schema the models accept), or rebuild them with model_validate(..., by_name=True) / populate_by_name on restore. A dedicated round-trip test with an alias-only field ("async": null on a function call) would guard this.
Workaround I am using until then: strip a null async_ from model_fields_set on relayed function calls before they reach the Runner, and restore the serialized state once at pause time to fail early instead of at resume.
- Ngôn ngữ chính
- Python
- Star
- 30k
- Fork
- 4.9k
- Merge trung bình
- 16 giờ 25 phút
- Pull request đã merge (30 ngày)
- 220
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của openai/openai-agents-python
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 77/100
openai/openai-agents-python#5348 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Support the OpenAI Decisions APIĐang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
openai/openai-agents-python#5353 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 70/100
openai/openai-agents-python#5342 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
openai/openai-agents-python#5336 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
openai/openai-agents-python#5322 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của openai/openai-agents-python
Issue tương tự
-
good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
btclib-org/btclib-node#1955 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation v0.9.2
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 72/100
khuisman/mcp-gee-sweet#1033 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
ossf/cve-bin-tool#5966 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 2 ngày
-
write_iceberg ignores the table's write.parquet.compression-codec propertyCó thể đã có người làm @YuangGao đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Eventual-Inc/Daft#7606 ·
Maintainer thường phản hồi trong vòng 1 ngày