Terminación

En la sección anterior, exploramos cómo definir agentes y organizarlos en equipos que puedan resolver tareas. Sin embargo, una ejecución puede continuar indefinidamente, y en muchos casos, necesitamos saber cuándo detenerla. Este es el papel de la condición de terminación.

Saptiva-Agents admite varias condiciones de terminación al proporcionar una clase base TerminationCondition y varias implementaciones que heredan de ella.

Una condición de terminación es una función callable que toma una secuencia de objetos AgentEvent o ChatMessage desde la última vez que se llamó a la condición, y devuelve un StopMessage si la conversación debe ser terminada, o None en caso contrario. Una vez que se ha alcanzado una condición de terminación, debe restablecerse llamando a reset() antes de poder utilizarse nuevamente.

Algunas cosas importantes a tener en cuenta sobre las condiciones:

  • Son con estado, pero se restablecen automáticamente después de que cada ejecución (run() o run_stream()) haya terminado.

  • Pueden combinarse utilizando los operadores AND y OR.

Nota

Para los equipos de chat en grupo (es decir, RoundRobinGroupChat y SelectorGroupChat), la condición de terminación se llama después de que cada agente responde. Aunque una respuesta puede contener múltiples mensajes internos, el equipo llama a su condición de terminación solo una vez para todos los mensajes de una sola respuesta. Por lo tanto, la condición se llama con la "secuencia delta" de mensajes desde la última vez que se llamó.

Condiciones de Terminación Incorporadas:

  1. MaxMessageTermination: Se detiene después de que se haya producido un número especificado de mensajes, incluyendo tanto mensajes del agente como del tarea.

  2. TextMentionTermination: Se detiene cuando se menciona un texto o cadena específica en un mensaje (por ejemplo, "TERMINATE").

  3. TokenUsageTermination: Se detiene cuando se utiliza un cierto número de tokens de solicitud o de finalización. Esto requiere que los agentes informen sobre el uso de tokens en sus mensajes.

  4. TimeoutTermination: Se detiene después de una duración especificada en segundos.

  5. HandoffTermination: Se detiene cuando se solicita la transferencia a un objetivo específico. Los mensajes de transferencia se pueden utilizar para crear patrones como Swarm. Esto es útil cuando se desea pausar la ejecución y permitir que la aplicación o el usuario proporcionen información cuando un agente les transfiere el control.

  6. SourceMatchTermination: Se detiene después de que un agente específico responde.

  7. ExternalTermination: Permite el control programático de la finalización desde fuera de la ejecución. Esto es útil para la integración con interfaces de usuario (por ejemplo, botones de "Detener" en interfaces de chat).

  8. StopMessageTermination: Se detiene cuando un StopMessage es producido por un agente.

  9. TextMessageTermination: Se detiene cuando un TextMessage es producido por un agente.

  10. FunctionCallTermination: Se detiene cuando un ToolCallExecutionEvent que contiene un FunctionExecutionResult con un nombre coincidente es producido por un agente.


Uso Básico

Para demostrar las características de las condiciones de terminación, crearemos un equipo compuesto por dos agentes: un agente principal responsable de la generación de texto y un agente crítico que revisa y proporciona comentarios sobre el texto generado.

from saptiva_agents import QWEN_MODEL
from saptiva_agents.agents import AssistantAgent
from saptiva_agents.conditions import MaxMessageTermination, TextMentionTermination
from saptiva_agents.teams import RoundRobinGroupChat
from saptiva_agents.ui import Console
from saptiva_agents.base import SaptivaAIChatCompletionClient

model_client = SaptivaAIChatCompletionClient(
    model=QWEN_MODEL,
    temperature=1,
    api_key="TU_SAPTIVA_API_KEY",
)

# Crear el agente principal.
primary_agent = AssistantAgent(
    "primary",
    model_client=model_client,
    system_message="You are a helpful AI assistant.",
)

# Crear al agente crítico.
critic_agent = AssistantAgent(
    "critic",
    model_client=model_client,
    system_message="Provide constructive feedback for every message. Respond with 'APPROVE' to when your feedbacks are addressed.",
)

Exploremos cómo las condiciones de terminación se restablecen automáticamente después de cada llamada a run o run_stream, lo que permite al equipo reanudar su conversación desde donde la dejó.

max_msg_termination = MaxMessageTermination(max_messages=3)
round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=max_msg_termination)

# Utiliza asyncio.run(...) si estás ejecutando este script de manera independiente.
await Console(round_robin_team.run_stream(task="Write a unique, Haiku about the weather in Paris"))
---------- user ----------
Write a unique, Haiku about the weather in Paris
---------- primary ----------
Gentle rain whispers,  
Cobblestones glisten softly—  
Paris dreams in gray.
[Prompt tokens: 30, Completion tokens: 19]
---------- critic ----------
The Haiku captures the essence of a rainy day in Paris beautifully, and the imagery is vivid. However, it's important to ensure the use of the traditional 5-7-5 syllable structure for Haikus. Your current Haiku lines are composed of 4-7-5 syllables, which slightly deviates from the form. Consider revising the first line to fit the structure.

For example:
Soft rain whispers down,  
Cobblestones glisten softly —  
Paris dreams in gray.

This revision maintains the essence of your original lines while adhering to the traditional Haiku structure.
[Prompt tokens: 70, Completion tokens: 120]
---------- Summary ----------
Number of messages: 3
Finish reason: Maximum number of messages 3 reached, current message count: 3
Total prompt tokens: 100
Total completion tokens: 139
Duration: 3.34 seconds
TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a unique, Haiku about the weather in Paris'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=30, completion_tokens=19), content='Gentle rain whispers,  \nCobblestones glisten softly—  \nParis dreams in gray.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=70, completion_tokens=120), content="The Haiku captures the essence of a rainy day in Paris beautifully, and the imagery is vivid. However, it's important to ensure the use of the traditional 5-7-5 syllable structure for Haikus. Your current Haiku lines are composed of 4-7-5 syllables, which slightly deviates from the form. Consider revising the first line to fit the structure.\n\nFor example:\nSoft rain whispers down,  \nCobblestones glisten softly —  \nParis dreams in gray.\n\nThis revision maintains the essence of your original lines while adhering to the traditional Haiku structure.")], stop_reason='Maximum number of messages 3 reached, current message count: 3')

La conversación se detuvo al alcanzar el límite máximo de mensajes. Como el agente principal no pudo responder a los comentarios, continuemos la conversación.

# Utiliza asyncio.run(...) si estás ejecutando este script de manera independiente.
await Console(round_robin_team.run_stream())
---------- primary ----------
Thank you for your feedback. Here is the revised Haiku:

Soft rain whispers down,  
Cobblestones glisten softly —  
Paris dreams in gray.
[Prompt tokens: 181, Completion tokens: 32]
---------- critic ----------
The revised Haiku now follows the traditional 5-7-5 syllable pattern, and it still beautifully captures the atmospheric mood of Paris in the rain. The imagery and flow are both clear and evocative. Well done on making the adjustment! 

APPROVE
[Prompt tokens: 234, Completion tokens: 54]
---------- primary ----------
Thank you for your kind words and approval. I'm glad the revision meets your expectations and captures the essence of Paris. If you have any more requests or need further assistance, feel free to ask!
[Prompt tokens: 279, Completion tokens: 39]
---------- Summary ----------
Number of messages: 3
Finish reason: Maximum number of messages 3 reached, current message count: 3
Total prompt tokens: 694
Total completion tokens: 125
Duration: 6.43 seconds
TaskResult(messages=[TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=181, completion_tokens=32), content='Thank you for your feedback. Here is the revised Haiku:\n\nSoft rain whispers down,  \nCobblestones glisten softly —  \nParis dreams in gray.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=234, completion_tokens=54), content='The revised Haiku now follows the traditional 5-7-5 syllable pattern, and it still beautifully captures the atmospheric mood of Paris in the rain. The imagery and flow are both clear and evocative. Well done on making the adjustment! \n\nAPPROVE'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=279, completion_tokens=39), content="Thank you for your kind words and approval. I'm glad the revision meets your expectations and captures the essence of Paris. If you have any more requests or need further assistance, feel free to ask!")], stop_reason='Maximum number of messages 3 reached, current message count: 3')

El equipo continuó desde donde lo dejó, permitiendo que el agente principal respondiera a la retroalimentación.


Combinación de Condiciones de Terminación

Vamos a mostrar cómo se pueden combinar las condiciones de terminación utilizando los operadores AND (&) y OR (|) para crear una lógica de terminación más compleja. Por ejemplo, crearemos un equipo que se detiene después de generar 10 mensajes o cuando el agente crítico aprueba un mensaje.

max_msg_termination = MaxMessageTermination(max_messages=10)
text_termination = TextMentionTermination("APPROVE")
combined_termination = max_msg_termination | text_termination

round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=combined_termination)

# Utiliza asyncio.run(...) si estás ejecutando este script de manera independiente.
await Console(round_robin_team.run_stream(task="Write a unique, Haiku about the weather in Paris"))
---------- user ----------
Write a unique, Haiku about the weather in Paris
---------- primary ----------
Spring breeze gently hums,  
Cherry blossoms in full bloom—  
Paris wakes to life.
[Prompt tokens: 467, Completion tokens: 19]
---------- critic ----------
The Haiku beautifully captures the awakening of Paris in the spring. The imagery of a gentle spring breeze and cherry blossoms in full bloom effectively conveys the rejuvenating feel of the season. The final line, "Paris wakes to life," encapsulates the renewed energy and vibrancy of the city. The Haiku adheres to the 5-7-5 syllable structure and portrays a vivid seasonal transformation in a concise and poetic manner. Excellent work!

APPROVE
[Prompt tokens: 746, Completion tokens: 93]
---------- Summary ----------
Number of messages: 3
Finish reason: Text 'APPROVE' mentioned
Total prompt tokens: 1213
Total completion tokens: 112
Duration: 2.75 seconds
TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a unique, Haiku about the weather in Paris'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=467, completion_tokens=19), content='Spring breeze gently hums,  \nCherry blossoms in full bloom—  \nParis wakes to life.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=746, completion_tokens=93), content='The Haiku beautifully captures the awakening of Paris in the spring. The imagery of a gentle spring breeze and cherry blossoms in full bloom effectively conveys the rejuvenating feel of the season. The final line, "Paris wakes to life," encapsulates the renewed energy and vibrancy of the city. The Haiku adheres to the 5-7-5 syllable structure and portrays a vivid seasonal transformation in a concise and poetic manner. Excellent work!\n\nAPPROVE')], stop_reason="Text 'APPROVE' mentioned")

La conversación se detuvo después de que el agente crítico aprobara el mensaje, aunque también podría haberse detenido si se generaban 10 mensajes.

Alternativamente, si queremos detener la ejecución solo cuando se cumplen ambas condiciones, podemos usar el operador AND (&).

combined_termination = max_msg_termination & text_termination

Condición de Terminación Personalizada

Las condiciones de terminación integradas son suficientes para la mayoría de los casos de uso. Sin embargo, puede haber casos en los que necesite implementar una condición de terminación personalizada que no se ajuste a las existentes. Puede hacerlo subclasificando la clase TerminationCondition .

En este ejemplo, creamos una condición de terminación personalizada que detiene la conversación cuando se realiza una llamada a una función específica.

from typing import Sequence

from saptiva_agents.base import TerminatedException, TerminationCondition
from saptiva_agents.messages import BaseAgentEvent, BaseChatMessage, StopMessage, ToolCallExecutionEvent
from saptiva_agents.core import Component
from pydantic import BaseModel
from typing_extensions import Self


class FunctionCallTerminationConfig(BaseModel):
    """
    Configuración del componente para la serialización y deserialización de la condición 
    de terminación.
    """
    function_name: str


class FunctionCallTermination(TerminationCondition, Component[FunctionCallTerminationConfig]):
    """Termina la conversación si se recibe un FunctionExecutionResult con un nombre específico."""

    component_config_schema = FunctionCallTerminationConfig
    """El esquema para la configuración del componente."""

    def __init__(self, function_name: str) -> None:
        self._terminated = False
        self._function_name = function_name

    @property
    def terminated(self) -> bool:
        return self._terminated

    async def __call__(self, messages: Sequence[BaseAgentEvent | BaseChatMessage]) -> StopMessage | None:
        if self._terminated:
            raise TerminatedException("La condición de terminación ya se ha alcanzado")
        for message in messages:
            if isinstance(message, ToolCallExecutionEvent):
                for execution in message.content:
                    if execution.name == self._function_name:
                        self._terminated = True
                        return StopMessage(
                            content=f"Function '{self._function_name}' was executed.",
                            source="FunctionCallTermination",
                        )
        return None

    async def reset(self) -> None:
        self._terminated = False

    def _to_config(self) -> FunctionCallTerminationConfig:
        return FunctionCallTerminationConfig(
            function_name=self._function_name,
        )

    @classmethod
    def _from_config(cls, config: FunctionCallTerminationConfig) -> Self:
        return cls(
            function_name=config.function_name,
        )

Usaremos esta nueva condición de terminación para detener la conversación cuando el agente crítico apruebe un mensaje usando la llamada a la función approve .

Primero, creamos una función simple que se llamará cuando el agente crítico apruebe un mensaje.

def approve() -> None:
    """Aprobar el mensaje cuando se hayan abordado todos los comentarios."""
    pass

Luego creamos los agentes. El agente crítico está equipado con la herramienta approve .

from saptiva_agents import QWEN_MODEL
from saptiva_agents.agents import AssistantAgent
from saptiva_agents.conditions import FunctionCallTermination
from saptiva_agents.teams import RoundRobinGroupChat
from saptiva_agents.ui import Console
from saptiva_agents.base import SaptivaAIChatCompletionClient

model_client = SaptivaAIChatCompletionClient(
    model=QWEN_MODEL,
    temperature=1,
    api_key="TU_SAPTIVA_API_KEY",
)

# Crear el agente principal.
primary_agent = AssistantAgent(
    "primary",
    model_client=model_client,
    system_message="You are a helpful AI assistant.",
)

# Crear el agente crítico con la función de aprobación como herramienta.
critic_agent = AssistantAgent(
    "critic",
    model_client=model_client,
    tools=[approve],  # Registrar la función de aprobación como una herramienta.
    system_message="Provide constructive feedback. Use the approve tool to approve when all feedbacks are addressed.",
)

Ahora, creamos la condición de terminación y el equipo. Ejecutamos el equipo con la tarea de escribir un poema.

function_call_termination = FunctionCallTermination(function_name="approve")
round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=function_call_termination)

# Utiliza asyncio.run(...) si estás ejecutando este script de manera independiente.
await Console(round_robin_team.run_stream(task="Write a unique, Haiku about the weather in Paris"))
await model_client.close()
---------- user ----------
Write a unique, Haiku about the weather in Paris
---------- primary ----------
Raindrops gently fall,  
Cobblestones shine in dim light—  
Paris dreams in grey.  
---------- critic ----------
This Haiku beautifully captures a melancholic yet romantic image of Paris in the rain. The use of sensory imagery like "Raindrops gently fall" and "Cobblestones shine" effectively paints a vivid picture. It could be interesting to experiment with more distinct seasonal elements of Paris, such as incorporating the Seine River or iconic landmarks in the context of the weather. Overall, it successfully conveys the atmosphere of Paris in subtle, poetic imagery.
---------- primary ----------
Thank you for your feedback! I’m glad you enjoyed the imagery. Here’s another Haiku that incorporates iconic Parisian elements:

Eiffel stands in mist,  
Seine's ripple mirrors the sky—  
Spring whispers anew.  
---------- critic ----------
[FunctionCall(id='call_QEWJZ873EG4UIEpsQHi1HsAu', arguments='{}', name='approve')]
---------- critic ----------
[FunctionExecutionResult(content='None', name='approve', call_id='call_QEWJZ873EG4UIEpsQHi1HsAu', is_error=False)]
---------- critic ----------
None
TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='Write a unique, Haiku about the weather in Paris', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=30, completion_tokens=23), metadata={}, content='Raindrops gently fall,  \nCobblestones shine in dim light—  \nParis dreams in grey.  ', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=99, completion_tokens=90), metadata={}, content='This Haiku beautifully captures a melancholic yet romantic image of Paris in the rain. The use of sensory imagery like "Raindrops gently fall" and "Cobblestones shine" effectively paints a vivid picture. It could be interesting to experiment with more distinct seasonal elements of Paris, such as incorporating the Seine River or iconic landmarks in the context of the weather. Overall, it successfully conveys the atmosphere of Paris in subtle, poetic imagery.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=152, completion_tokens=48), metadata={}, content="Thank you for your feedback! I’m glad you enjoyed the imagery. Here’s another Haiku that incorporates iconic Parisian elements:\n\nEiffel stands in mist,  \nSeine's ripple mirrors the sky—  \nSpring whispers anew.  ", type='TextMessage'), ToolCallRequestEvent(source='critic', models_usage=RequestUsage(prompt_tokens=246, completion_tokens=11), metadata={}, content=[FunctionCall(id='call_QEWJZ873EG4UIEpsQHi1HsAu', arguments='{}', name='approve')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='critic', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='None', name='approve', call_id='call_QEWJZ873EG4UIEpsQHi1HsAu', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='critic', models_usage=None, metadata={}, content='None', type='ToolCallSummaryMessage')], stop_reason="Function 'approve' was executed.")

Puedes ver que la conversación se detuvo cuando el agente crítico aprobó el mensaje usando la llamada a la función approve .

Última actualización