RunState round trip drops alias-only fields: `"async": null` on a function call is serialized as `async_` and breaks `get_interruptions()` on restore
Los mantenedores suelen responder en 1 día
@iveteamorim ya está trabajando en esto.
Desde el 12/10/2026.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 58/100
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- Python
- Estrellas
- 30k
- Forks
- 4.9k
- Merge medio
- 18 h 13 min
- PR fusionados (30 d)
- 215
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de openai/openai-agents-python
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
openai/openai-agents-python#5348 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Support the OpenAI Decisions APIAbierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
openai/openai-agents-python#5353 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 Medio día Aptitud para principiantes 70/100
openai/openai-agents-python#5342 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 3/5 1-2 días Aptitud para principiantes 65/100
openai/openai-agents-python#5336 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Finished tool call is dropped when a parallel tool's guardrail tripsPosiblemente ocupada @jbeckwith-oai la tomó hace 7 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
openai/openai-agents-python#5327 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de openai/openai-agents-python
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Deltares/imod-python#1940 ·
Los mantenedores suelen responder en 1 día
-
effort: low prio: low ready
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
digitalfabrik/integreat-cms#4621 ·
Los mantenedores suelen responder en 5 días
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
ioos/compliance-checker#1375 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
netbox status: backlog topic: testing type: housekeeping
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
netbox-community/netbox#23395 ·
Los mantenedores suelen responder en 1 día