2024-11-15 10:12:45 -08:00
|
|
|
import time
|
2024-11-25 10:09:06 -08:00
|
|
|
from typing import List, Sequence
|
2024-10-22 19:23:02 +01:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
from autogen_core import Component
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from typing_extensions import Self
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
from ..base import TerminatedException, TerminationCondition
|
2024-12-23 16:10:46 -08:00
|
|
|
from ..messages import AgentEvent, ChatMessage, HandoffMessage, MultiModalMessage, StopMessage
|
2024-10-22 19:23:02 +01:00
|
|
|
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
class StopMessageTerminationConfig(BaseModel):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class StopMessageTermination(TerminationCondition, Component[StopMessageTerminationConfig]):
|
2024-10-22 19:23:02 +01:00
|
|
|
"""Terminate the conversation if a StopMessage is received."""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = StopMessageTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.StopMessageTermination"
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
def __init__(self) -> None:
|
|
|
|
self._terminated = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-10-22 19:23:02 +01:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
for message in messages:
|
|
|
|
if isinstance(message, StopMessage):
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(content="Stop message received", source="StopMessageTermination")
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._terminated = False
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> StopMessageTerminationConfig:
|
|
|
|
return StopMessageTerminationConfig()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: StopMessageTerminationConfig) -> Self:
|
|
|
|
return cls()
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
class MaxMessageTerminationConfig(BaseModel):
|
|
|
|
max_messages: int
|
|
|
|
|
|
|
|
|
|
|
|
class MaxMessageTermination(TerminationCondition, Component[MaxMessageTerminationConfig]):
|
2024-10-22 19:23:02 +01:00
|
|
|
"""Terminate the conversation after a maximum number of messages have been exchanged.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
max_messages: The maximum number of messages allowed in the conversation.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = MaxMessageTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.MaxMessageTermination"
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
def __init__(self, max_messages: int) -> None:
|
|
|
|
self._max_messages = max_messages
|
|
|
|
self._message_count = 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._message_count >= self._max_messages
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-10-22 19:23:02 +01:00
|
|
|
if self.terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
self._message_count += len(messages)
|
|
|
|
if self._message_count >= self._max_messages:
|
|
|
|
return StopMessage(
|
2024-10-29 18:37:26 -07:00
|
|
|
content=f"Maximum number of messages {self._max_messages} reached, current message count: {self._message_count}",
|
2024-10-22 19:23:02 +01:00
|
|
|
source="MaxMessageTermination",
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._message_count = 0
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> MaxMessageTerminationConfig:
|
|
|
|
return MaxMessageTerminationConfig(max_messages=self._max_messages)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: MaxMessageTerminationConfig) -> Self:
|
|
|
|
return cls(max_messages=config.max_messages)
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
class TextMentionTerminationConfig(BaseModel):
|
|
|
|
text: str
|
|
|
|
|
|
|
|
|
|
|
|
class TextMentionTermination(TerminationCondition, Component[TextMentionTerminationConfig]):
|
2024-10-22 19:23:02 +01:00
|
|
|
"""Terminate the conversation if a specific text is mentioned.
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
Args:
|
|
|
|
text: The text to look for in the messages.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = TextMentionTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.TextMentionTermination"
|
|
|
|
|
2024-10-22 19:23:02 +01:00
|
|
|
def __init__(self, text: str) -> None:
|
|
|
|
self._text = text
|
|
|
|
self._terminated = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-10-22 19:23:02 +01:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
for message in messages:
|
2024-12-23 16:10:46 -08:00
|
|
|
if isinstance(message.content, str) and self._text in message.content:
|
2024-10-22 19:23:02 +01:00
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination")
|
|
|
|
elif isinstance(message, MultiModalMessage):
|
|
|
|
for item in message.content:
|
|
|
|
if isinstance(item, str) and self._text in item:
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination")
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._terminated = False
|
2024-11-01 15:01:43 -07:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> TextMentionTerminationConfig:
|
|
|
|
return TextMentionTerminationConfig(text=self._text)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: TextMentionTerminationConfig) -> Self:
|
|
|
|
return cls(text=config.text)
|
|
|
|
|
2024-11-01 15:01:43 -07:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
class TokenUsageTerminationConfig(BaseModel):
|
|
|
|
max_total_token: int | None
|
|
|
|
max_prompt_token: int | None
|
|
|
|
max_completion_token: int | None
|
|
|
|
|
|
|
|
|
|
|
|
class TokenUsageTermination(TerminationCondition, Component[TokenUsageTerminationConfig]):
|
2024-11-01 15:01:43 -07:00
|
|
|
"""Terminate the conversation if a token usage limit is reached.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
max_total_token: The maximum total number of tokens allowed in the conversation.
|
|
|
|
max_prompt_token: The maximum number of prompt tokens allowed in the conversation.
|
|
|
|
max_completion_token: The maximum number of completion tokens allowed in the conversation.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
ValueError: If none of max_total_token, max_prompt_token, or max_completion_token is provided.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = TokenUsageTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.TokenUsageTermination"
|
|
|
|
|
2024-11-01 15:01:43 -07:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
max_total_token: int | None = None,
|
|
|
|
max_prompt_token: int | None = None,
|
|
|
|
max_completion_token: int | None = None,
|
|
|
|
) -> None:
|
|
|
|
if max_total_token is None and max_prompt_token is None and max_completion_token is None:
|
|
|
|
raise ValueError(
|
|
|
|
"At least one of max_total_token, max_prompt_token, or max_completion_token must be provided"
|
|
|
|
)
|
|
|
|
self._max_total_token = max_total_token
|
|
|
|
self._max_prompt_token = max_prompt_token
|
|
|
|
self._max_completion_token = max_completion_token
|
|
|
|
self._total_token_count = 0
|
|
|
|
self._prompt_token_count = 0
|
|
|
|
self._completion_token_count = 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return (
|
|
|
|
(self._max_total_token is not None and self._total_token_count >= self._max_total_token)
|
|
|
|
or (self._max_prompt_token is not None and self._prompt_token_count >= self._max_prompt_token)
|
|
|
|
or (self._max_completion_token is not None and self._completion_token_count >= self._max_completion_token)
|
|
|
|
)
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-11-01 15:01:43 -07:00
|
|
|
if self.terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
for message in messages:
|
2024-11-04 09:25:53 -08:00
|
|
|
if message.models_usage is not None:
|
|
|
|
self._prompt_token_count += message.models_usage.prompt_tokens
|
|
|
|
self._completion_token_count += message.models_usage.completion_tokens
|
|
|
|
self._total_token_count += message.models_usage.prompt_tokens + message.models_usage.completion_tokens
|
2024-11-01 15:01:43 -07:00
|
|
|
if self.terminated:
|
|
|
|
content = f"Token usage limit reached, total token count: {self._total_token_count}, prompt token count: {self._prompt_token_count}, completion token count: {self._completion_token_count}."
|
|
|
|
return StopMessage(content=content, source="TokenUsageTermination")
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._total_token_count = 0
|
|
|
|
self._prompt_token_count = 0
|
|
|
|
self._completion_token_count = 0
|
2024-11-10 21:38:52 -08:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> TokenUsageTerminationConfig:
|
|
|
|
return TokenUsageTerminationConfig(
|
|
|
|
max_total_token=self._max_total_token,
|
|
|
|
max_prompt_token=self._max_prompt_token,
|
|
|
|
max_completion_token=self._max_completion_token,
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: TokenUsageTerminationConfig) -> Self:
|
|
|
|
return cls(
|
|
|
|
max_total_token=config.max_total_token,
|
|
|
|
max_prompt_token=config.max_prompt_token,
|
|
|
|
max_completion_token=config.max_completion_token,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class HandoffTerminationConfig(BaseModel):
|
|
|
|
target: str
|
2024-11-10 21:38:52 -08:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
|
|
|
|
class HandoffTermination(TerminationCondition, Component[HandoffTerminationConfig]):
|
2024-11-10 21:38:52 -08:00
|
|
|
"""Terminate the conversation if a :class:`~autogen_agentchat.messages.HandoffMessage`
|
|
|
|
with the given target is received.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
target (str): The target of the handoff message.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = HandoffTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.HandoffTermination"
|
|
|
|
|
2024-11-10 21:38:52 -08:00
|
|
|
def __init__(self, target: str) -> None:
|
|
|
|
self._terminated = False
|
|
|
|
self._target = target
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-11-10 21:38:52 -08:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
for message in messages:
|
|
|
|
if isinstance(message, HandoffMessage) and message.target == self._target:
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(
|
|
|
|
content=f"Handoff to {self._target} from {message.source} detected.", source="HandoffTermination"
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._terminated = False
|
2024-11-15 10:12:45 -08:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> HandoffTerminationConfig:
|
|
|
|
return HandoffTerminationConfig(target=self._target)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: HandoffTerminationConfig) -> Self:
|
|
|
|
return cls(target=config.target)
|
2024-11-15 10:12:45 -08:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
|
|
|
|
class TimeoutTerminationConfig(BaseModel):
|
|
|
|
timeout_seconds: float
|
|
|
|
|
|
|
|
|
|
|
|
class TimeoutTermination(TerminationCondition, Component[TimeoutTerminationConfig]):
|
2024-11-15 10:12:45 -08:00
|
|
|
"""Terminate the conversation after a specified duration has passed.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
timeout_seconds: The maximum duration in seconds before terminating the conversation.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = TimeoutTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.TimeoutTermination"
|
|
|
|
|
2024-11-15 10:12:45 -08:00
|
|
|
def __init__(self, timeout_seconds: float) -> None:
|
|
|
|
self._timeout_seconds = timeout_seconds
|
|
|
|
self._start_time = time.monotonic()
|
|
|
|
self._terminated = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-11-15 10:12:45 -08:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
|
|
|
|
if (time.monotonic() - self._start_time) >= self._timeout_seconds:
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(
|
|
|
|
content=f"Timeout of {self._timeout_seconds} seconds reached", source="TimeoutTermination"
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._start_time = time.monotonic()
|
|
|
|
self._terminated = False
|
2024-11-21 03:25:53 -05:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> TimeoutTerminationConfig:
|
|
|
|
return TimeoutTerminationConfig(timeout_seconds=self._timeout_seconds)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: TimeoutTerminationConfig) -> Self:
|
|
|
|
return cls(timeout_seconds=config.timeout_seconds)
|
2024-11-21 03:25:53 -05:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
|
|
|
|
class ExternalTerminationConfig(BaseModel):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ExternalTermination(TerminationCondition, Component[ExternalTerminationConfig]):
|
2024-11-21 03:25:53 -05:00
|
|
|
"""A termination condition that is externally controlled
|
|
|
|
by calling the :meth:`set` method.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
2024-12-03 15:24:25 -08:00
|
|
|
from autogen_agentchat.conditions import ExternalTermination
|
2024-11-25 16:07:45 -05:00
|
|
|
|
2024-11-21 03:25:53 -05:00
|
|
|
termination = ExternalTermination()
|
|
|
|
|
|
|
|
# Run the team in an asyncio task.
|
|
|
|
...
|
|
|
|
|
|
|
|
# Set the termination condition externally
|
|
|
|
termination.set()
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = ExternalTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.ExternalTermination"
|
|
|
|
|
2024-11-21 03:25:53 -05:00
|
|
|
def __init__(self) -> None:
|
|
|
|
self._terminated = False
|
|
|
|
self._setted = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
|
|
|
def set(self) -> None:
|
2024-11-21 19:24:12 -05:00
|
|
|
"""Set the termination condition to terminated."""
|
2024-11-21 03:25:53 -05:00
|
|
|
self._setted = True
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-11-21 03:25:53 -05:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
if self._setted:
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(content="External termination requested", source="ExternalTermination")
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._terminated = False
|
|
|
|
self._setted = False
|
2024-11-23 22:07:21 +07:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
def _to_config(self) -> ExternalTerminationConfig:
|
|
|
|
return ExternalTerminationConfig()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: ExternalTerminationConfig) -> Self:
|
|
|
|
return cls()
|
|
|
|
|
2024-11-23 22:07:21 +07:00
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
class SourceMatchTerminationConfig(BaseModel):
|
|
|
|
sources: List[str]
|
|
|
|
|
|
|
|
|
|
|
|
class SourceMatchTermination(TerminationCondition, Component[SourceMatchTerminationConfig]):
|
2024-11-23 22:07:21 +07:00
|
|
|
"""Terminate the conversation after a specific source responds.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sources (List[str]): List of source names to terminate the conversation.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
TerminatedException: If the termination condition has already been reached.
|
|
|
|
"""
|
|
|
|
|
2025-01-13 19:30:30 -08:00
|
|
|
component_config_schema = SourceMatchTerminationConfig
|
|
|
|
component_provider_override = "autogen_agentchat.conditions.SourceMatchTermination"
|
|
|
|
|
2024-11-23 22:07:21 +07:00
|
|
|
def __init__(self, sources: List[str]) -> None:
|
|
|
|
self._sources = sources
|
|
|
|
self._terminated = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def terminated(self) -> bool:
|
|
|
|
return self._terminated
|
|
|
|
|
2024-12-18 14:09:19 -08:00
|
|
|
async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:
|
2024-11-23 22:07:21 +07:00
|
|
|
if self._terminated:
|
|
|
|
raise TerminatedException("Termination condition has already been reached")
|
|
|
|
if not messages:
|
|
|
|
return None
|
|
|
|
for message in messages:
|
|
|
|
if message.source in self._sources:
|
|
|
|
self._terminated = True
|
|
|
|
return StopMessage(content=f"'{message.source}' answered", source="SourceMatchTermination")
|
|
|
|
return None
|
|
|
|
|
|
|
|
async def reset(self) -> None:
|
|
|
|
self._terminated = False
|
2025-01-13 19:30:30 -08:00
|
|
|
|
|
|
|
def _to_config(self) -> SourceMatchTerminationConfig:
|
|
|
|
return SourceMatchTerminationConfig(sources=self._sources)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _from_config(cls, config: SourceMatchTerminationConfig) -> Self:
|
|
|
|
return cls(sources=config.sources)
|