‎
loginstudio
  • Overview
  • COMIENZA
    • Quickstart
  • Basicos
    • Modelos Disponibles
    • TEXT API (NEW)
    • TEXT API (ACTUAL)
    • Formas de pago
  • Mejores prácticas
    • RAG
    • Prompteo
  • Saptiva Agents
    • Introducción
    • Instalación
    • Quick Start
    • Tutorial
      • Modelos
      • Mensajes
      • Agentes
      • Equipos
      • Human-in-the-Loop
      • Terminación
      • Manejo De Estados
    • Avanzado
      • Agentes Personalizados
      • Selector Group Chat
      • Memoria
      • Logging
      • Serialización
    • Conceptos Del Núcleo
      • Quick Start
      • Aplicaciones De Agentes & Multi-Agentes
      • Entornos De Ejecución Para Agentes
      • Pila De Aplicación
      • Identidad & Ciclo De Vida Del Agente
      • Tema & Suscripción (Topic & Subscription)
    • Guía De Framework
      • Agente & Entorno De Ejecución De Agentes
      • Mensaje & Comunicación
      • Open Telemetry
    • Guía De Componentes
      • Cliente De Modelo
      • Contexto De Modelo
      • Herramientas (Tools)
    • Patrones De Diseño Multi-Agente
      • Agentes Concurrentes
      • Flujo de Trabajo Secuencial
      • Transferencia De Tareas (Handoffs)
      • Mezcla De Agentes (Mixture Of Agents)
      • Multi-Agent Debate
      • Reflexión (Reflection)
    • Ejemplos
      • Planificación De Viajes
      • Investigación De Empresas
      • Revisión De Literatura
    • PyPi
  • Manuales
  • Model cards
    • Quickstart
      • Model Card: DeepSeek R1 Lite
      • Model Card: LLAMA3.3 70B
      • Model Card: Saptiva Turbo
      • Model Card: Phi 4
      • Model Card: Qwen
      • Model Card: Gemma 3
  • DEFINICIONES
    • Temperature
Con tecnología de GitBook
En esta página
  • Agente de Cuenta Regresiva
  • Agente Aritmético
  • Uso de Clientes de Modelos Personalizados en Agentes Personalizados
  1. Saptiva Agents
  2. Avanzado

Agentes Personalizados

Puedes tener agentes con comportamientos que no encajan en una configuración predefinida. En esos casos, puedes construir agentes personalizados.

Todos los agentes en AgentChat heredan de la clase BaseChatAgent e implementan los siguientes métodos y atributos abstractos:

  • on_messages(): El método abstracto que define el comportamiento del agente en respuesta a los mensajes. Este método se llama cuando se le pide al agente que proporcione una respuesta mediante run(). Devuelve un objeto Response.

  • on_reset(): El método abstracto que reinicia el agente a su estado inicial. Este método se llama cuando se solicita al agente que se reinicie.

  • produced_message_types: La lista de tipos de mensajes BaseChatMessage que el agente puede producir en su respuesta.

Opcionalmente, puedes implementar el método on_messages_stream() para transmitir mensajes a medida que son generados por el agente. Si este método no está implementado, el agente utiliza la implementación predeterminada de on_messages_stream() que llama a on_messages() y emite todos los mensajes en la respuesta.


Agente de Cuenta Regresiva

En este ejemplo, creamos un agente simple que cuenta regresivamente desde un número dado hasta cero, y produce un flujo de mensajes con el conteo actual.

from typing import AsyncGenerator, List, Sequence

from saptiva_agents.agents import BaseChatAgent
from saptiva_agents.base import Response
from saptiva_agents.messages import BaseAgentEvent, BaseChatMessage, TextMessage
from saptiva_agents.core import CancellationToken


class CountDownAgent(BaseChatAgent):
    def __init__(self, name: str, count: int = 3):
        super().__init__(name, "A simple agent that counts down.")
        self._count = count

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

    async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
        # Llama a on_messages_stream.
        response: Response | None = None
        async for message in self.on_messages_stream(messages, cancellation_token):
            if isinstance(message, Response):
                response = message
        assert response is not None
        return response

    async def on_messages_stream(
        self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken
    ) -> AsyncGenerator[BaseAgentEvent | BaseChatMessage | Response, None]:
        inner_messages: List[BaseAgentEvent | BaseChatMessage] = []
        for i in range(self._count, 0, -1):
            msg = TextMessage(content=f"{i}...", source=self.name)
            inner_messages.append(msg)
            yield msg
        # La respuesta se devuelve al final del stream.
        # Contiene el mensaje final y todos los mensajes internos.
        yield Response(chat_message=TextMessage(content="Done!", source=self.name), inner_messages=inner_messages)

    async def on_reset(self, cancellation_token: CancellationToken) -> None:
        pass


async def run_countdown_agent() -> None:
    # Crear un agente de cuenta regresiva.
    countdown_agent = CountDownAgent("countdown")

    # Ejecutar el agente con una tarea dada y transmitir la respuesta.
    async for message in countdown_agent.on_messages_stream([], CancellationToken()):
        if isinstance(message, Response):
            print(message.chat_message)
        else:
            print(message)


# Usar asyncio.run(run_countdown_agent()) al ejecutar en un script.
await run_countdown_agent()
3...
2...
1...
Done!

Agente Aritmético

En este ejemplo, creamos una clase de agente capaz de realizar operaciones aritméticas simples sobre un número entero dado. Luego, utilizaremos distintas instancias de esta clase de agente dentro de un SelectorGroupChat para transformar un número entero en otro mediante la aplicación de una secuencia de operaciones aritméticas.

La clase ArithmeticAgent recibe una función llamada operator_func, la cual toma un número entero y devuelve otro número entero tras aplicarle una operación aritmética. En su método on_messages, aplica la función operator_func al número recibido en el mensaje de entrada y devuelve una respuesta con el resultado.

from typing import Callable, Sequence

from saptiva_agents.agents import BaseChatAgent
from saptiva_agents.base import SaptivaAIChatCompletionClient, Response
from saptiva_agents.conditions import MaxMessageTermination
from saptiva_agents.messages import BaseChatMessage
from saptiva_agents.teams import SelectorGroupChat
from saptiva_agents.ui import Console
from saptiva_agents.core import CancellationToken


class ArithmeticAgent(BaseChatAgent):
    def __init__(self, name: str, description: str, operator_func: Callable[[int], int]) -> None:
        super().__init__(name, description=description)
        self._operator_func = operator_func
        self._message_history: List[BaseChatMessage] = []

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

    async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
        # Actualizar el historial de mensajes.
        # NOTA: es posible que la lista de mensajes esté vacía, lo que significa que el agente fue seleccionado previamente.
        self._message_history.extend(messages)
        # Extraer el número del último mensaje recibido.
        assert isinstance(self._message_history[-1], TextMessage)
        number = int(self._message_history[-1].content)
        # Aplicar la función de operación al número.
        result = self._operator_func(number)
        # Crear un nuevo mensaje con el resultado.
        response_message = TextMessage(content=str(result), source=self.name)
        # Actualizar nuevamente el historial de mensajes.
        self._message_history.append(response_message)
        # Retornar la respuesta generada
        return Response(chat_message=response_message)

    async def on_reset(self, cancellation_token: CancellationToken) -> None:
        pass

Nota

El método on_messages puede ser llamado con una lista vacía de mensajes. Esto significa que el agente fue seleccionado anteriormente y ahora es llamado nuevamente, pero sin recibir nuevos mensajes por parte del emisor. Por lo tanto, es importante mantener un historial de los mensajes recibidos previamente por el agente y utilizar dicho historial para generar la respuesta.

Ahora podemos crear un SelectorGroupChat con 5 instancias del ArithmeticAgent:

  • Una que suma 1 al número entero recibido.

  • Una que resta 1 al número entero recibido.

  • Una que multiplica por 2 al número entero recibido.

  • Una que divide entre 2 al número entero recibido y redondea hacia abajo al entero más cercano.

  • Una que retorna el número entero recibido sin cambios.

Luego, creamos un SelectorGroupChat con estos agentes y configuramos adecuadamente el selector:

  • Permitimos seleccionar consecutivamente el mismo agente, para posibilitar operaciones repetidas.

  • Personalizamos el mensaje del selector (selector_prompt) para adaptar la respuesta del modelo a la tarea específica.

async def run_number_agents() -> None:
    # Crear agentes para operaciones numéricas.
    add_agent = ArithmeticAgent("add_agent", "Suma 1 al número.", lambda x: x + 1)
    multiply_agent = ArithmeticAgent("multiply_agent", "Multiplica el número por 2.", lambda x: x * 2)
    subtract_agent = ArithmeticAgent("subtract_agent", "Resta 1 al número.", lambda x: x - 1)
    divide_agent = ArithmeticAgent("divide_agent", "Divide el número entre 2 y redondea hacia abajo.", lambda x: x // 2)
    identity_agent = ArithmeticAgent("identity_agent", "Retorna el número tal como está.", lambda x: x)

    # Condición de terminación: detener después de 10 mensajes.
    termination_condition = MaxMessageTermination(10)

    # Crear un SelectorGroupChat con agentes específicos.
    selector_group_chat = SelectorGroupChat(
        [add_agent, multiply_agent, subtract_agent, divide_agent, identity_agent],
        model_client=OpenAIChatCompletionClient(model="gpt-4o"),
        termination_condition=termination_condition,
        allow_repeated_speaker=True,  # Permitir que el mismo agente pueda hablar varias veces, necesario para esta tarea.
        selector_prompt=(
            "Roles disponibles:\n{roles}\nDescripción de sus funciones:\n{participants}\n"
            "Historial actual de la conversación:\n{history}\n"
            "Selecciona el rol más apropiado para el siguiente mensaje, y retorna solo el nombre del rol."
        ),
    )

    # Ejecutar el selector group chat con una tarea específica y transmitir la respuesta.
    task: List[BaseChatMessage] = [
        TextMessage(content="Aplica las operaciones para convertir el número dado en 25.", source="user"),
        TextMessage(content="10", source="user"),
    ]
    stream = selector_group_chat.run_stream(task=task)
    await Console(stream)


# Usa asyncio.run(run_number_agents()) cuando ejecutes desde un script.
await run_number_agents()
---------- user ----------
Aplica las operaciones para convertir el número dado en 25.
---------- user ----------
10
---------- multiply_agent ----------
20
---------- add_agent ----------
21
---------- multiply_agent ----------
42
---------- divide_agent ----------
21
---------- add_agent ----------
22
---------- add_agent ----------
23
---------- add_agent ----------
24
---------- add_agent ----------
25
---------- Summary ----------
Number of messages: 10
Finish reason: Maximum number of messages 10 reached, current message count: 10
Total prompt tokens: 0
Total completion tokens: 0
Duration: 2.40 seconds

Del resultado, podemos ver que los agentes han transformado con éxito el número entero de entrada de 10 a 25 eligiendo los agentes apropiados que aplican las operaciones aritméticas en secuencia.


Uso de Clientes de Modelos Personalizados en Agentes Personalizados

Una de las características clave del agente predefinido AssistantAgent en AgentChat es que acepta un argumento model_client y puede utilizarlo para responder a los mensajes. Sin embargo, en algunos casos podrías necesitar que tu agente utilice un cliente de modelo personalizado.

Puedes lograr esto mediante la creación de un agente personalizado que implemente tu propio cliente de modelo.

from typing import Sequence

from saptiva_agents import QWEN_MODEL
from saptiva_agents.agents import BaseChatAgent
from saptiva_agents.base import SaptivaAIChatCompletionClient, Response
from saptiva_agents.core import UnboundedChatCompletionContext, CancellationToken
from saptiva_agents.messages import TextMessage, BaseChatMessage


class QwenAssistantAgent(BaseChatAgent):
    def __init__(
        self,
        name: str,
        description: str = "Un agente que brinda asistencia con la capacidad de usar herramientas.",
        model: str = QWEN_MODEL,
        api_key: str = "TU_SAPTIVA_API_KEY",
        system_message: str
        | None = "Eres un asistente útil que puede responder a los mensajes. Responde con TERMINATE cuando se ha completado la tarea.",
    ):
        super().__init__(name=name, description=description)
        self._model_context = UnboundedChatCompletionContext()
        self._model_client = self.get_model_client(api_key=api_key)
        self._system_message = system_message
        self._model = model

    def get_model_client(self, api_key):
        return SaptivaAIChatCompletionClient(
            model=self._model,
            api_key=api_key,
        )

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

    async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
        final_response = None
        async for message in self.on_messages_stream(messages, cancellation_token):
            if isinstance(message, Response):
                final_response = message

        if final_response is None:
            raise AssertionError("La transmisión debería haber devuelto el resultado final.")

        return final_response

    async def on_reset(self, cancellation_token: CancellationToken) -> None:
        """Restablezca el asistente limpiando el contexto del modelo."""
        await self._model_context.clear()
qwen_assistant = QwenAssistantAgent("qwen_assistant")
await Console(gemini_assistant.run_stream(task="What is the capital of New York?"))
---------- user ----------
What is the capital of New York?
---------- qwen_assistant ----------
Albany
TERMINATE
TaskResult(messages=[TextMessage(source='user', models_usage=None, content='What is the capital of New York?', type='TextMessage'), TextMessage(source='qwen_assistant', models_usage=RequestUsage(prompt_tokens=46, completion_tokens=5), content='Albany\nTERMINATE\n', type='TextMessage')], stop_reason=None)

En el ejemplo anterior, hemos optado por proporcionar model, api_key y system_message como argumentos. Sin embargo, tú puedes elegir proporcionar cualquier otro argumento que sea requerido por el cliente de modelo que estés utilizando o que encaje con el diseño de tu aplicación.

Ahora exploremos cómo utilizar este agente personalizado como parte de un equipo en Saptiva-Agents.

from saptiva_agents.agents import AssistantAgent
from saptiva_agents.conditions import TextMentionTermination
from saptiva_agents.teams import RoundRobinGroupChat
from saptiva_agents.ui import Console

# Crear el agente principal.
primary_agent = AssistantAgent(
    "primary",
    model_client=SaptivaAIChatCompletionClient(model="llama3.3:70b", api_key="TU_SAPTIVA_API_KEY"),
    system_message="You are a helpful AI assistant.",
)

# Crear al agente crítico basado en nuestro nuevo QwenAssistantAgent.
qwen_critic_agent = QwenAssistantAgent(
    "qwen_critic",
    system_message="Provide constructive feedback. Respond with 'APPROVE' to when your feedbacks are addressed.",
)

# Defina una condición de terminación que detenga la tarea si el crítico aprueba o después de 10 mensajes.
termination = TextMentionTermination("APPROVE") | MaxMessageTermination(10)

# Cree un equipo con los agentes primarios y críticos.
team = RoundRobinGroupChat([primary_agent, qwen_critic_agent], termination_condition=termination)

await Console(team.run_stream(task="Write a Haiku poem with 4 lines about the fall season."))
---------- user ----------
Write a Haiku poem with 4 lines about the fall season.
---------- primary ----------
Crimson leaves cascade,  
Whispering winds sing of change,  
Chill wraps the fading,  
Nature's quilt, rich and warm.
---------- qwen_critic ----------
The poem is good, but it has four lines instead of three.  A haiku must have three lines with a 5-7-5 syllable structure.  The content is evocative of autumn, but the form is incorrect.  Please revise to adhere to the haiku's syllable structure.

---------- primary ----------
Thank you for your feedback! Here’s a revised haiku that follows the 5-7-5 syllable structure:

Crimson leaves drift down,  
Chill winds whisper through the gold,  
Autumn’s breath is near.
---------- qwen_critic ----------
The revised haiku is much improved.  It correctly follows the 5-7-5 syllable structure and maintains the evocative imagery of autumn.  APPROVE
TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a Haiku poem with 4 lines about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=33, completion_tokens=31), content="Crimson leaves cascade,  \nWhispering winds sing of change,  \nChill wraps the fading,  \nNature's quilt, rich and warm.", type='TextMessage'), TextMessage(source='qwen_critic', models_usage=RequestUsage(prompt_tokens=86, completion_tokens=60), content="The poem is good, but it has four lines instead of three.  A haiku must have three lines with a 5-7-5 syllable structure.  The content is evocative of autumn, but the form is incorrect.  Please revise to adhere to the haiku's syllable structure.\n", type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=141, completion_tokens=49), content='Thank you for your feedback! Here’s a revised haiku that follows the 5-7-5 syllable structure:\n\nCrimson leaves drift down,  \nChill winds whisper through the gold,  \nAutumn’s breath is near.', type='TextMessage'), TextMessage(source='qwen_critic', models_usage=RequestUsage(prompt_tokens=211, completion_tokens=32), content='The revised haiku is much improved.  It correctly follows the 5-7-5 syllable structure and maintains the evocative imagery of autumn.  APPROVE\n', type='TextMessage')], stop_reason="Text 'APPROVE' mentioned")

En la sección anterior mostramos varios conceptos muy importantes:

  • Hemos desarrollado un agente personalizado que utiliza 2 modelos para responder mensajes.

  • Demostramos que este agente personalizado puede utilizarse dentro del ecosistema general de Saptiva-Agents, en este caso como participante en un RoundRobinGroupChat, siempre que herede de la clase BaseChatAgent.

AnteriorAvanzadoSiguienteSelector Group Chat

Última actualización hace 1 mes