Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

クローズ
#5,352 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@iveteamorim がすでに取り組んでいます。

2026年10月12日 から。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
58/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
ai, backend

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
Python
スター
30k
フォーク
4.9k
平均マージ
18時間 13分
マージ済み PR(30日)
215

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

openai/openai-agents-python のほかの issue

openai/openai-agents-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。