Skip to content

Added RunErrorDetails object for MaxTurnsExceeded exception #743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from .models.openai_chatcompletions import OpenAIChatCompletionsModel
from .models.openai_provider import OpenAIProvider
from .models.openai_responses import OpenAIResponsesModel
from .result import RunResult, RunResultStreaming
from .result import RunErrorDetails, RunResult, RunResultStreaming
from .run import RunConfig, Runner
from .run_context import RunContextWrapper, TContext
from .stream_events import (
Expand Down Expand Up @@ -204,6 +204,7 @@ def enable_verbose_stdout_logging():
"AgentHooks",
"RunContextWrapper",
"TContext",
"RunErrorDetails",
"RunResult",
"RunResultStreaming",
"RunConfig",
Expand Down
7 changes: 5 additions & 2 deletions src/agents/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of Optiona, you can from __future__ import annotations then do error_details: ErrorDetails | None


if TYPE_CHECKING:
from .guardrail import InputGuardrailResult, OutputGuardrailResult
from .result import RunErrorDetails


class AgentsException(Exception):
Expand All @@ -12,9 +13,11 @@ class MaxTurnsExceeded(AgentsException):
"""Exception raised when the maximum number of turns is exceeded."""

message: str
run_error_details: Optional["RunErrorDetails"]

def __init__(self, message: str):
def __init__(self, message: str, run_error_details: Optional["RunErrorDetails"] = None):
self.message = message
self.run_error_details = run_error_details


class ModelBehaviorError(AgentsException):
Expand Down
18 changes: 17 additions & 1 deletion src/agents/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
from .run_context import RunContextWrapper
from .stream_events import StreamEvent
from .tracing import Trace
from .util._pretty_print import pretty_print_result, pretty_print_run_result_streaming
from .util._pretty_print import (
pretty_print_result,
pretty_print_run_error_details,
pretty_print_run_result_streaming,
)

if TYPE_CHECKING:
from ._run_impl import QueueCompleteSentinel
Expand Down Expand Up @@ -244,3 +248,15 @@ def _cleanup_tasks(self):

def __str__(self) -> str:
return pretty_print_run_result_streaming(self)

@dataclass
class RunErrorDetails(RunResultBase):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to subclass RunResultBase. The error details and result can/should diverge - for example, final_output doesn't make sense in error details, because by definition there is no error.

_last_agent: Agent[Any]

@property
def last_agent(self) -> Agent[Any]:
"""The last agent that was run."""
return self._last_agent

def __str__(self) -> str:
return pretty_print_run_error_details(self)
17 changes: 15 additions & 2 deletions src/agents/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from .model_settings import ModelSettings
from .models.interface import Model, ModelProvider
from .models.multi_provider import MultiProvider
from .result import RunResult, RunResultStreaming
from .result import RunErrorDetails, RunResult, RunResultStreaming
from .run_context import RunContextWrapper, TContext
from .stream_events import AgentUpdatedStreamEvent, RawResponsesStreamEvent
from .tool import Tool
Expand Down Expand Up @@ -208,7 +208,20 @@ async def run(
data={"max_turns": max_turns},
),
)
raise MaxTurnsExceeded(f"Max turns ({max_turns}) exceeded")
run_error_details = RunErrorDetails(
input=original_input,
new_items=generated_items,
raw_responses=model_responses,
final_output=None,
input_guardrail_results=input_guardrail_results,
output_guardrail_results=[],
context_wrapper=context_wrapper,
_last_agent=current_agent
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might make more sense to create this in the outermost try-catch. You'll catch all exceptions there, so you can capture and re-raise

raise MaxTurnsExceeded(
f"Max turns ({max_turns}) exceeded",
run_error_details
)

logger.debug(
f"Running agent {current_agent.name} (turn {current_turn})",
Expand Down
11 changes: 10 additions & 1 deletion src/agents/util/_pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import BaseModel

if TYPE_CHECKING:
from ..result import RunResult, RunResultBase, RunResultStreaming
from ..result import RunErrorDetails, RunResult, RunResultBase, RunResultStreaming


def _indent(text: str, indent_level: int) -> str:
Expand Down Expand Up @@ -37,6 +37,15 @@ def pretty_print_result(result: "RunResult") -> str:

return output

def pretty_print_run_error_details(result: "RunErrorDetails") -> str:
output = "RunErrorDetails:"
output += f'\n- Last agent: Agent(name="{result.last_agent.name}", ...)'
output += f"\n- {len(result.new_items)} new item(s)"
output += f"\n- {len(result.raw_responses)} raw response(s)"
output += f"\n- {len(result.input_guardrail_results)} input guardrail result(s)"
output += "\n(See `RunErrorDetails` for more details)"

return output

def pretty_print_run_result_streaming(result: "RunResultStreaming") -> str:
output = "RunResultStreaming:"
Expand Down