Skip to content
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

Exception Handling Policy #5268

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CancellationToken,
ClosureAgent,
ComponentBase,
ExceptionHandlingPolicy,
MessageContext,
SingleThreadedAgentRuntime,
TypeSubscription,
Expand Down Expand Up @@ -45,6 +46,7 @@ def __init__(
group_chat_manager_class: type[SequentialRoutedAgent],
termination_condition: TerminationCondition | None = None,
max_turns: int | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
):
if len(participants) == 0:
raise ValueError("At least one participant is required.")
Expand All @@ -70,7 +72,7 @@ def __init__(

# Create a runtime for the team.
# TODO: The runtime should be created by a managed context.
self._runtime = SingleThreadedAgentRuntime()
self._runtime = SingleThreadedAgentRuntime(exception_handling_policy=exception_handling_policy)

# Flag to track if the group chat has been initialized.
self._initialized = False
Expand All @@ -87,6 +89,7 @@ def _create_group_chat_manager_factory(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> Callable[[], SequentialRoutedAgent]: ...

def _create_participant_factory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from abc import ABC, abstractmethod
from typing import Any, List

from autogen_core import DefaultTopicId, MessageContext, event, rpc
from autogen_core import DefaultTopicId, ExceptionHandlingPolicy, MessageContext, event, rpc

from ...base import TerminationCondition
from ...messages import AgentEvent, ChatMessage, StopMessage
Expand Down Expand Up @@ -36,6 +36,7 @@ def __init__(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None = None,
max_turns: int | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
):
super().__init__(description="Group chat manager")
self._group_topic_type = group_topic_type
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import Callable, List

from autogen_core import Component, ComponentModel
from autogen_core import Component, ComponentModel, ExceptionHandlingPolicy
from autogen_core.models import ChatCompletionClient
from pydantic import BaseModel
from typing_extensions import Self
Expand Down Expand Up @@ -99,12 +99,14 @@ def __init__(
max_turns: int | None = 20,
max_stalls: int = 3,
final_answer_prompt: str = ORCHESTRATOR_FINAL_ANSWER_PROMPT,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
):
super().__init__(
participants,
group_chat_manager_class=MagenticOneOrchestrator,
termination_condition=termination_condition,
max_turns=max_turns,
exception_handling_policy=exception_handling_policy,
)

# Validate the participants.
Expand All @@ -122,6 +124,7 @@ def _create_group_chat_manager_factory(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> Callable[[], MagenticOneOrchestrator]:
return lambda: MagenticOneOrchestrator(
group_topic_type,
Expand All @@ -133,6 +136,7 @@ def _create_group_chat_manager_factory(
self._max_stalls,
self._final_answer_prompt,
termination_condition,
exception_handling_policy=exception_handling_policy,
)

def _to_config(self) -> MagenticOneGroupChatConfig:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
import logging
from typing import Any, Dict, List, Mapping

from autogen_core import AgentId, CancellationToken, DefaultTopicId, Image, MessageContext, event, rpc
from autogen_core import (
AgentId,
CancellationToken,
DefaultTopicId,
ExceptionHandlingPolicy,
Image,
MessageContext,
event,
rpc,
)
from autogen_core.models import (
AssistantMessage,
ChatCompletionClient,
Expand Down Expand Up @@ -60,6 +69,7 @@ def __init__(
max_stalls: int,
final_answer_prompt: str,
termination_condition: TerminationCondition | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
):
super().__init__(
group_topic_type,
Expand All @@ -68,6 +78,7 @@ def __init__(
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy=exception_handling_policy,
)
self._model_client = model_client
self._max_stalls = max_stalls
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Callable, List, Mapping

from autogen_core import Component, ComponentModel
from autogen_core import Component, ComponentModel, ExceptionHandlingPolicy
from pydantic import BaseModel
from typing_extensions import Self

Expand All @@ -22,6 +22,7 @@ def __init__(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> None:
super().__init__(
group_topic_type,
Expand All @@ -30,6 +31,7 @@ def __init__(
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy,
)
self._next_speaker_index = 0

Expand Down Expand Up @@ -153,12 +155,14 @@ def __init__(
participants: List[ChatAgent],
termination_condition: TerminationCondition | None = None,
max_turns: int | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> None:
super().__init__(
participants,
group_chat_manager_class=RoundRobinGroupChatManager,
termination_condition=termination_condition,
max_turns=max_turns,
exception_handling_policy=exception_handling_policy,
)

def _create_group_chat_manager_factory(
Expand All @@ -169,6 +173,7 @@ def _create_group_chat_manager_factory(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> Callable[[], RoundRobinGroupChatManager]:
def _factory() -> RoundRobinGroupChatManager:
return RoundRobinGroupChatManager(
Expand All @@ -178,6 +183,7 @@ def _factory() -> RoundRobinGroupChatManager:
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy=exception_handling_policy,
)

return _factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from typing import Any, Callable, Dict, List, Mapping, Sequence

from autogen_core import Component, ComponentModel
from autogen_core import Component, ComponentModel, ExceptionHandlingPolicy
from autogen_core.models import ChatCompletionClient, SystemMessage
from pydantic import BaseModel
from typing_extensions import Self
Expand Down Expand Up @@ -39,6 +39,7 @@ def __init__(
selector_prompt: str,
allow_repeated_speaker: bool,
selector_func: Callable[[Sequence[AgentEvent | ChatMessage]], str | None] | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> None:
super().__init__(
group_topic_type,
Expand All @@ -47,6 +48,7 @@ def __init__(
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy,
)
self._model_client = model_client
self._selector_prompt = selector_prompt
Expand Down Expand Up @@ -357,12 +359,14 @@ def __init__(
""",
allow_repeated_speaker: bool = False,
selector_func: Callable[[Sequence[AgentEvent | ChatMessage]], str | None] | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
):
super().__init__(
participants,
group_chat_manager_class=SelectorGroupChatManager,
termination_condition=termination_condition,
max_turns=max_turns,
exception_handling_policy=exception_handling_policy,
)
# Validate the participants.
if len(participants) < 2:
Expand All @@ -387,6 +391,7 @@ def _create_group_chat_manager_factory(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> Callable[[], BaseGroupChatManager]:
return lambda: SelectorGroupChatManager(
group_topic_type,
Expand All @@ -399,6 +404,7 @@ def _create_group_chat_manager_factory(
self._selector_prompt,
self._allow_repeated_speaker,
self._selector_func,
exception_handling_policy=exception_handling_policy,
)

def _to_config(self) -> SelectorGroupChatConfig:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Callable, List, Mapping

from autogen_core import Component, ComponentModel
from autogen_core import Component, ComponentModel, ExceptionHandlingPolicy
from pydantic import BaseModel

from ...base import ChatAgent, TerminationCondition
Expand All @@ -21,6 +21,7 @@ def __init__(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> None:
super().__init__(
group_topic_type,
Expand All @@ -29,6 +30,7 @@ def __init__(
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy=exception_handling_policy,
)
self._current_speaker = participant_topic_types[0]

Expand Down Expand Up @@ -199,12 +201,14 @@ def __init__(
participants: List[ChatAgent],
termination_condition: TerminationCondition | None = None,
max_turns: int | None = None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> None:
super().__init__(
participants,
group_chat_manager_class=SwarmGroupChatManager,
termination_condition=termination_condition,
max_turns=max_turns,
exception_handling_policy=exception_handling_policy,
)
# The first participant must be able to produce handoff messages.
first_participant = self._participants[0]
Expand All @@ -219,6 +223,7 @@ def _create_group_chat_manager_factory(
participant_descriptions: List[str],
termination_condition: TerminationCondition | None,
max_turns: int | None,
exception_handling_policy: ExceptionHandlingPolicy | None = ExceptionHandlingPolicy.IGNORE_AND_LOG,
) -> Callable[[], SwarmGroupChatManager]:
def _factory() -> SwarmGroupChatManager:
return SwarmGroupChatManager(
Expand All @@ -228,6 +233,7 @@ def _factory() -> SwarmGroupChatManager:
participant_descriptions,
termination_condition,
max_turns,
exception_handling_policy=exception_handling_policy,
)

return _factory
Expand Down
65 changes: 64 additions & 1 deletion python/packages/autogen-agentchat/tests/test_group_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from autogen_agentchat.teams._group_chat._selector_group_chat import SelectorGroupChatManager
from autogen_agentchat.teams._group_chat._swarm_group_chat import SwarmGroupChatManager
from autogen_agentchat.ui import Console
from autogen_core import AgentId, CancellationToken, FunctionCall
from autogen_core import AgentId, CancellationToken, ExceptionHandlingPolicy, FunctionCall
from autogen_core.models import (
AssistantMessage,
FunctionExecutionResult,
Expand Down Expand Up @@ -100,6 +100,27 @@ async def on_reset(self, cancellation_token: CancellationToken) -> None:
self._last_message = None


class _FlakyAgent(BaseChatAgent):
def __init__(self, name: str, description: str) -> None:
super().__init__(name, description)
self._last_message: str | None = None
self._total_messages = 0

@property
def produced_message_types(self) -> Sequence[type[ChatMessage]]:
return (TextMessage,)

@property
def total_messages(self) -> int:
return self._total_messages

async def on_messages(self, messages: Sequence[ChatMessage], cancellation_token: CancellationToken) -> Response:
raise Exception("I am a flaky agent...")

async def on_reset(self, cancellation_token: CancellationToken) -> None:
self._last_message = None


class _StopAgent(_EchoAgent):
def __init__(self, name: str, description: str, *, stop_at: int = 1) -> None:
super().__init__(name, description)
Expand Down Expand Up @@ -400,6 +421,48 @@ async def test_round_robin_group_chat_with_resume_and_reset() -> None:
assert result.stop_reason is not None


@pytest.mark.asyncio
async def test_round_robin_group_chat_with_exception_handling_policy_raise() -> None:
agent_1 = _EchoAgent("agent_1", description="echo agent 1")
agent_2 = _FlakyAgent("agent_2", description="echo agent 2")
agent_3 = _EchoAgent("agent_3", description="echo agent 3")
termination = MaxMessageTermination(3)
team = RoundRobinGroupChat(
participants=[agent_1, agent_2, agent_3],
termination_condition=termination,
exception_handling_policy=ExceptionHandlingPolicy.RAISE,
)

with pytest.raises(BaseException) as exc_info:
await team.run(
task="Write a program that prints 'Hello, world!'",
)

assert str(exc_info.value.__cause__) == "I am a flaky agent..."


@pytest.mark.asyncio
async def test_round_robin_group_chat_with_exception_handling_policy_ignore_and_log() -> None:
agent_1 = _EchoAgent("agent_1", description="echo agent 1")
agent_2 = _FlakyAgent("agent_2", description="echo agent 2")
agent_3 = _EchoAgent("agent_3", description="echo agent 3")
termination = MaxMessageTermination(3)
team = RoundRobinGroupChat(
participants=[agent_1, agent_2, agent_3],
termination_condition=termination,
exception_handling_policy=ExceptionHandlingPolicy.IGNORE_AND_LOG,
)

result = await team.run(
task="Write a program that prints 'Hello, world!'",
)

assert len(result.messages) == 2
assert result.messages[1].source == "agent_1"
assert result.messages[2].source == "agent_3"
assert result.stop_reason is not None


@pytest.mark.asyncio
async def test_round_robin_group_chat_max_turn() -> None:
agent_1 = _EchoAgent("agent_1", description="echo agent 1")
Expand Down
2 changes: 2 additions & 0 deletions python/packages/autogen-core/src/autogen_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
)
from ._default_subscription import DefaultSubscription, default_subscription, type_subscription
from ._default_topic import DefaultTopicId
from ._exception_handling_policy import ExceptionHandlingPolicy
from ._image import Image
from ._intervention import (
DefaultInterventionHandler,
Expand Down Expand Up @@ -132,4 +133,5 @@
"DropMessage",
"InterventionHandler",
"DefaultInterventionHandler",
"ExceptionHandlingPolicy",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import Enum


class ExceptionHandlingPolicy(Enum):
IGNORE_AND_LOG = "IGNORE_AND_LOG"
RAISE = "RAISE"
Loading
Loading