Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

RunState round trip drops alias-only fields: `"async": null` on a function call is serialized as `async_` and breaks `get_interruptions()` on restore

Đã đóng
#5,352 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

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
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
ai, backend

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 / RunState serialization).
  • Have you searched for related issues? Yes. #5162 (created_by surviving model_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 in RunState round 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:

  1. openai parses it into async_ = None and marks the field as set.
  2. The paused RunState is serialized as "async_": null (10 places in my dump, including current_step.data.interruptions[0].raw_item).
  3. On restore, ResponseFunctionToolCall(**data) only knows the alias async, so async_ lands in model_extra.
  4. get_interruptions() → _copy_tool_approval_raw_item → _copy_pydantic_value raises TypeError("Colliding key in tool approval model extras") because the extra key collides with a declared field, and get_interruptions() re-raises it as UserError with 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 on v0.23.1, the latest release)
  • Related library versions: openai 3.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 async flag on function calls; the repro below uses a custom Model so 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) without by_alias
  • agents/run_state.py:3170 / :3225: restore via ResponseFunctionToolCall(**data), which only accepts the alias
  • agents/run_state.py:439 _copy_pydantic_value: Colliding key in tool approval model extras
  • agents/run_state.py:1046-1056 get_interruptions: wraps it into UserError with raise ... from None, so the original TypeError is 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

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của openai/openai-agents-python

Tất cả issue của openai/openai-agents-python

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.