RunState round trip drops alias-only fields: `"async": null` on a function call is serialized as `async_` and breaks `get_interruptions()` on restore
I maintainer di solito rispondono entro 1 giorno
@iveteamorim ci sta già lavorando.
Dal 12/10/2026.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 58/100
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Python
- Stelle
- 29.9k
- Fork
- 4.8k
- Merge medio
- 22h 17m
- PR unite (30g)
- 208
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di openai/openai-agents-python
-
Difficoltà 1/5 1-3 ore Idoneità per principianti 87/100
openai/openai-agents-python#5350 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
SandboxApplyPatchTool splits patch lines with str.splitlines(), so a form feed or in a line breaks Add/Update FileForse già presa @abhishekacharya200 l’ha presa 1 giorno fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
openai/openai-agents-python#5349 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
openai/openai-agents-python#5348 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
openai/openai-agents-python#5353 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 55/100
openai/openai-agents-python#5351 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di openai/openai-agents-python
Issue simili
-
area: agents area: docs good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
openapi/gknowledge#23 ·
-
get() for StrEnumForse già presa @FeJo5952 l’ha presa oggi. Apertagood first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
collective/icalendar#1921 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Improvement Python Spans
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
getsentry/sentry-python#7994 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
alphagov/notifications-api#5004 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
equinor/radix-flux#3922 ·
I maintainer di solito rispondono entro 1 giorno