11from __future__ import annotations
22
3- from dataclasses import dataclass
4- from typing import TYPE_CHECKING , Any
3+ from dataclasses import dataclass , replace
4+ from textwrap import dedent
5+ from typing import TYPE_CHECKING , Any , Optional
56
67if TYPE_CHECKING :
78 from .agent import Agent
89 from .guardrail import InputGuardrailResult , OutputGuardrailResult
910 from .items import ModelResponse , RunItem , TResponseInputItem
1011 from .run_context import RunContextWrapper
12+ from .run import RunConfig
13+ from .result import RunResult
1114 from .tool_guardrails import (
1215 ToolGuardrailFunctionOutput ,
1316 ToolInputGuardrail ,
@@ -28,6 +31,7 @@ class RunErrorDetails:
2831 context_wrapper : RunContextWrapper [Any ]
2932 input_guardrail_results : list [InputGuardrailResult ]
3033 output_guardrail_results : list [OutputGuardrailResult ]
34+ run_config : RunConfig
3135
3236 def __str__ (self ) -> str :
3337 return pretty_print_run_error_details (self )
@@ -48,10 +52,99 @@ class MaxTurnsExceeded(AgentsException):
4852
4953 message : str
5054
55+ _DEFAULT_RESUME_PROMPT = """
56+ You reached the maximum number of turns.
57+ Return a final answer to the query using ONLY the information already gathered in the conversation so far.
58+ """
59+
5160 def __init__ (self , message : str ):
5261 self .message = message
5362 super ().__init__ (message )
5463
64+ def resume (self , prompt : Optional [str ] = _DEFAULT_RESUME_PROMPT ) -> RunResult :
65+ """Resume the failed run synchronously with a final, tool-free turn.
66+
67+ Args:
68+ prompt: Optional user instruction to append before rerunning the final turn.
69+ Pass ``None`` to skip injecting an extra message; defaults to a reminder
70+ to produce a final answer from existing context.
71+ """
72+ run_data = self ._require_run_data ()
73+ inputs , run_config = self ._prepare_resume_arguments (run_data , prompt )
74+
75+ from .run import Runner
76+
77+ return Runner .run_sync (
78+ starting_agent = run_data .last_agent ,
79+ input = inputs ,
80+ context = run_data .context_wrapper .context ,
81+ max_turns = 1 ,
82+ run_config = run_config ,
83+ )
84+
85+ async def resume_async (self , prompt : Optional [str ] = _DEFAULT_RESUME_PROMPT ) -> RunResult :
86+ """Resume the failed run asynchronously with a final, tool-free turn.
87+
88+ Args:
89+ prompt: Optional user instruction to append before rerunning the final turn.
90+ Pass ``None`` to skip injecting an extra message; defaults to a reminder
91+ to produce a final answer from existing context.
92+ """
93+ run_data = self ._require_run_data ()
94+ inputs , run_config = self ._prepare_resume_arguments (run_data , prompt )
95+
96+ from .run import Runner
97+
98+ return await Runner .run (
99+ starting_agent = run_data .last_agent ,
100+ input = inputs ,
101+ context = run_data .context_wrapper .context ,
102+ max_turns = 1 ,
103+ run_config = run_config ,
104+ )
105+
106+ def _prepare_resume_arguments (
107+ self ,
108+ run_data : RunErrorDetails ,
109+ prompt : Optional [str ] = None ,
110+ ) -> tuple [list [TResponseInputItem ], RunConfig ]:
111+ from .items import ItemHelpers
112+ from .model_settings import ModelSettings
113+
114+ history : list [TResponseInputItem ] = ItemHelpers .input_to_new_input_list (run_data .input )
115+ for item in run_data .new_items :
116+ history .append (item .to_input_item ())
117+
118+ normalized_prompt = self ._normalize_resume_prompt (prompt )
119+ if normalized_prompt is not None :
120+ history .append ({"content" : normalized_prompt , "role" : "user" })
121+
122+ run_config = replace (run_data .run_config )
123+ if run_config .model_settings is None :
124+ run_config .model_settings = ModelSettings (tool_choice = "none" )
125+ else :
126+ run_config .model_settings = run_config .model_settings .resolve (
127+ ModelSettings (tool_choice = "none" )
128+ )
129+
130+ return (
131+ history ,
132+ run_config ,
133+ )
134+
135+ def _normalize_resume_prompt (self , prompt : Optional [str ]) -> Optional [str ]:
136+ if prompt is None :
137+ return None
138+ normalized = dedent (prompt ).strip ()
139+ return normalized or None
140+
141+ def _require_run_data (self ) -> RunErrorDetails :
142+ if self .run_data is None :
143+ raise RuntimeError (
144+ "Run data is not available; resume() can only be called on exceptions raised by Runner."
145+ )
146+ return self .run_data
147+
55148
56149class ModelBehaviorError (AgentsException ):
57150 """Exception raised when the model does something unexpected, e.g. calling a tool that doesn't
0 commit comments