‎
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
  • Protocolo de Mensajes
  • Agente Solucionador
  • Agente Agregador
  • Configuración de un Debate
  • Resolviendo Problemas Matemáticos
  1. Saptiva Agents
  2. Patrones De Diseño Multi-Agente

Multi-Agent Debate

AnteriorMezcla De Agentes (Mixture Of Agents)SiguienteReflexión (Reflection)

Última actualización hace 29 días

Multi-Agent Debate es un patrón de diseño multiagente que simula una interacción de varios turnos donde, en cada turno, los agentes intercambian sus respuestas entre sí y refinan dichas respuestas basándose en lo que reciben de otros agentes.

Este ejemplo muestra una implementación del patrón multi-agent debate para resolver problemas matemáticos del .

Hay dos tipos de agentes en este patrón: agentes solucionadores (solvers) y un agente agregador. Los agentes solucionadores están conectados de forma dispersa (sparse) siguiendo la técnica descrita en . Los solucionadores son responsables de resolver los problemas matemáticos e intercambiar respuestas con otros agentes. El agregador se encarga de distribuir los problemas a los solucionadores, esperar sus respuestas finales y agregarlas para obtener la respuesta final.

🔄 Funcionamiento del patrón:

  1. El usuario envía un problema matemático al agente agregador.

  2. El agregador distribuye el problema a los agentes solucionadores.

  3. Cada solucionador procesa el problema y publica una respuesta a sus vecinos.

  4. Cada solucionador usa las respuestas de sus vecinos para refinar su propia respuesta, y publica una nueva.

  5. Se repite el paso 4 durante un número fijo de rondas. En la última ronda, cada solucionador publica una respuesta final.

  6. El agregador utiliza votación por mayoría para agregar las respuestas finales y obtener la respuesta definitiva, que luego se publica.

Nota

Usaremos la API de difusión publish_message() y la suscripción por temas (topics) para implementar la topología de comunicación. Consulta la sección para entender su funcionamiento.

import re
from dataclasses import dataclass
from typing import Dict, List

from saptiva_agents import LLAMA_MODEL
from saptiva_agents.core import (
    DefaultTopicId,
    MessageContext,
    RoutedAgent,
    SingleThreadedAgentRuntime,
    TypeSubscription,
    default_subscription,
    message_handler,
)
from saptiva_agents.models import (
    AssistantMessage,
    LLMMessage,
    SystemMessage,
    UserMessage,
)
from saptiva_agents.base import SaptivaAIChatCompletionClient

Protocolo de Mensajes

Primero, definimos los mensajes utilizados por los agentes. IntermediateSolverResponse es el mensaje intercambiado entre los agentes solucionadores en cada ronda, y FinalSolverResponse es el mensaje publicado por los agentes solucionadores en la ronda final.

@dataclass
class Question:
    content: str


@dataclass
class Answer:
    content: str


@dataclass
class SolverRequest:
    content: str
    question: str


@dataclass
class IntermediateSolverResponse:
    content: str
    question: str
    answer: str
    round: int


@dataclass
class FinalSolverResponse:
    answer: str

Agente Solucionador

El agente solucionador es responsable de resolver problemas matemáticos e intercambiar respuestas con otros agentes solucionadores. Al recibir un SolverRequest, el agente solucionador utiliza un modelo LLM para generar una respuesta. Luego, publica un IntermediateSolverResponse o un FinalSolverResponse dependiendo del número de ronda.

Al agente solucionador se le asigna un tipo de tópico, el cual se utiliza para indicar el tópico al que debe publicar las respuestas intermedias. Este tópico es suscrito por sus agentes vecinos para recibir respuestas de este agente — más adelante mostraremos cómo se realiza esto.

Usamos default_subscription() para permitir que los agentes solucionadores se suscriban al tópico por defecto, que es el utilizado por el agente agregador para recolectar las respuestas finales de los agentes solucionadores.

@default_subscription
class MathSolver(RoutedAgent):
    def __init__(self, model_client: SaptivaAIChatCompletionClient, topic_type: str, num_neighbors: int, max_round: int) -> None:
        super().__init__("A debator.")
        self._topic_type = topic_type
        self._model_client = model_client
        self._num_neighbors = num_neighbors
        self._history: List[LLMMessage] = []
        self._buffer: Dict[int, List[IntermediateSolverResponse]] = {}
        self._system_messages = [
            SystemMessage(
                content=(
                    "You are a helpful assistant with expertise in mathematics and reasoning. "
                    "Your task is to assist in solving a math reasoning problem by providing "
                    "a clear and detailed solution. Limit your output within 100 words, "
                    "and your final answer should be a single numerical number, "
                    "in the form of {{answer}}, at the end of your response. "
                    "For example, 'The answer is {{42}}.'"
                )
            )
        ]
        self._round = 0
        self._max_round = max_round

    @message_handler
async def handle_request(self, message: SolverRequest, ctx: MessageContext) -> None:
    # Agregar la pregunta a la memoria.
    self._history.append(UserMessage(content=message.content, source="user"))
    # Hacer una inferencia usando el modelo.
    model_result = await self._model_client.create(self._system_messages + self._history)
    assert isinstance(model_result.content, str)
    # Agregar la respuesta a la memoria.
    self._history.append(AssistantMessage(content=model_result.content, source=self.metadata["type"]))
    print(f"{'-'*80}\nSolver {self.id} round {self._round}:\n{model_result.content}")
    # Extraer la respuesta del resultado.
    match = re.search(r"\{\{(\-?\d+(\.\d+)?)\}\}", model_result.content)
    if match is None:
        raise ValueError("La respuesta del modelo no contiene la solución.")
    answer = match.group(1)
    # Incrementar el contador.
    self._round += 1
    if self._round == self._max_round:
        # Si el contador alcanza el máximo de rondas, publica una respuesta final.
        await self.publish_message(FinalSolverResponse(answer=answer), topic_id=DefaultTopicId())
    else:
        # Publicar respuesta intermedia al tópico asociado con este solucionador.
        await self.publish_message(
            IntermediateSolverResponse(
                content=model_result.content,
                question=message.question,
                answer=answer,
                round=self._round,
            ),
            topic_id=DefaultTopicId(type=self._topic_type),
        )

@message_handler
async def handle_response(self, message: IntermediateSolverResponse, ctx: MessageContext) -> None:
    # Agregar la respuesta del vecino al búfer.
    self._buffer.setdefault(message.round, []).append(message)
    # Verificar si todos los vecinos han respondido.
    if len(self._buffer[message.round]) == self._num_neighbors:
        print(
            f"{'-'*80}\nSolver {self.id} round {message.round}:\nReceived all responses from {self._num_neighbors} neighbors."
        )
        # Preparar el prompt para la siguiente pregunta.
        prompt = "These are the solutions to the problem from other agents:\n"
        for resp in self._buffer[message.round]:
            prompt += f"One agent solution: {resp.content}\n"
        prompt += (
            "Using the solutions from other agents as additional information, "
            "can you provide your answer to the math problem? "
            f"The original math problem is {message.question}. "
            "Your final answer should be a single numerical number, "
            "in the form of {{answer}}, at the end of your response."
        )
        # Enviar la pregunta al propio agente para resolverla.
        await self.send_message(SolverRequest(content=prompt, question=message.question), self.id)
        # Limpiar el búfer.
        self._buffer.pop(message.round)

Agente Agregador

El agente agregador es responsable de manejar la pregunta del usuario y distribuir los problemas matemáticos a los agentes solucionadores.

El agregador se suscribe al tópico por defecto utilizando default_subscription(). Este tópico por defecto se utiliza para recibir la pregunta del usuario, recolectar las respuestas finales de los agentes solucionadores y publicar la respuesta final de nuevo al usuario.

En una aplicación más compleja, donde se desea aislar el debate multiagente como un subcomponente, se debería utilizar type_subscription() para definir un tipo de tópico específico para la comunicación entre agregador y solucionadores, y hacer que tanto el solucionador como el agregador publiquen y se suscriban a ese tipo de tópico.

@default_subscription
class MathAggregator(RoutedAgent):
    def __init__(self, num_solvers: int) -> None:
        super().__init__("Math Aggregator")
        self._num_solvers = num_solvers
        self._buffer: List[FinalSolverResponse] = []

    @message_handler
    async def handle_question(self, message: Question, ctx: MessageContext) -> None:
        print(f"{'-'*80}\nAggregator {self.id} received question:\n{message.content}")
        prompt = (
            f"Can you solve the following math problem?\n{message.content}\n"
            "Explain your reasoning. Your final answer should be a single numerical number, "
            "in the form of {{answer}}, at the end of your response."
        )
        print(f"{'-'*80}\nAggregator {self.id} publishes initial solver request.")
        await self.publish_message(SolverRequest(content=prompt, question=message.content), topic_id=DefaultTopicId())

    @message_handler
    async def handle_final_solver_response(self, message: FinalSolverResponse, ctx: MessageContext) -> None:
        self._buffer.append(message)
        if len(self._buffer) == self._num_solvers:
            print(f"{'-'*80}\nAggregator {self.id} received all final answers from {self._num_solvers} solvers.")
            # Encontrar la respuesta mayoritaria.
            answers = [resp.answer for resp in self._buffer]
            majority_answer = max(set(answers), key=answers.count)
            # Publicar la respuesta agregada.
            await self.publish_message(Answer(content=majority_answer), topic_id=DefaultTopicId())
            # Limpiar las respuestas.
            self._buffer.clear()
            print(f"{'-'*80}\nAggregator {self.id} publishes final answer:\n{majority_answer}")

Configuración de un Debate

Ahora configuraremos un debate multiagente con 4 agentes solucionadores y 1 agente agregador. Los agentes solucionadores estarán conectados de forma dispersa, como se ilustra en la figura a continuación:

A --- B
|     |
|     |
C --- D

Cada agente solucionador está conectado a otros dos agentes solucionadores. Por ejemplo, el agente A está conectado a los agentes B y C.

Primero, vamos a crear un runtime y registrar los tipos de agentes.

runtime = SingleThreadedAgentRuntime()

model_client = SaptivaAIChatCompletionClient(
    model=LLAMA_MODEL,
    api_key="TU_SAPTIVA_API_KEY",
)

await MathSolver.register(
    runtime,
    "MathSolverA",
    lambda: MathSolver(
        model_client=model_client,
        topic_type="MathSolverA",
        num_neighbors=2,
        max_round=3,
    ),
)
await MathSolver.register(
    runtime,
    "MathSolverB",
    lambda: MathSolver(
        model_client=model_client,
        topic_type="MathSolverB",
        num_neighbors=2,
        max_round=3,
    ),
)
await MathSolver.register(
    runtime,
    "MathSolverC",
    lambda: MathSolver(
        model_client=model_client,
        topic_type="MathSolverC",
        num_neighbors=2,
        max_round=3,
    ),
)
await MathSolver.register(
    runtime,
    "MathSolverD",
    lambda: MathSolver(
        model_client=model_client,
        topic_type="MathSolverD",
        num_neighbors=2,
        max_round=3,
    ),
)
await MathAggregator.register(runtime, "MathAggregator", lambda: MathAggregator(num_solvers=4))
AgentType(type='MathAggregator')

Ahora crearemos la topología de los agentes solucionadores utilizando TypeSubscription, que asigna el tipo de tema de publicación de cada agente solucionador a los tipos de agente de sus vecinos.

# Suscripciones para el tema publicado por MathSolverA.
await runtime.add_subscription(TypeSubscription("MathSolverA", "MathSolverD"))
await runtime.add_subscription(TypeSubscription("MathSolverA", "MathSolverB"))

# Suscripciones para el tema publicado por MathSolverB.
await runtime.add_subscription(TypeSubscription("MathSolverB", "MathSolverA"))
await runtime.add_subscription(TypeSubscription("MathSolverB", "MathSolverC"))

# Suscripciones para el tema publicado por MathSolverC.
await runtime.add_subscription(TypeSubscription("MathSolverC", "MathSolverB"))
await runtime.add_subscription(TypeSubscription("MathSolverC", "MathSolverD"))

# Suscripciones para el tema publicado por MathSolverD.
await runtime.add_subscription(TypeSubscription("MathSolverD", "MathSolverC"))
await runtime.add_subscription(TypeSubscription("MathSolverD", "MathSolverA"))

# Todos los solucionadores y el agregador se suscriben al tema predeterminado.

Resolviendo Problemas Matemáticos

Ahora ejecutemos el debate para resolver un problema matemático. Publicamos un SolverRequest en el tema por defecto (default topic), y el agente agregador iniciará el debate.

question = "Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?"
runtime.start()
await runtime.publish_message(Question(content=question), DefaultTopicId())
# Esperar a que el runtime se detenga cuando esté inactivo.
await runtime.stop_when_idle()
# Cerrar la conexión con el cliente del modelo.
await model_client.close()
--------------------------------------------------------------------------------
Aggregator MathAggregator:default received question:
Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?
--------------------------------------------------------------------------------
Aggregator MathAggregator:default publishes initial solver request.
--------------------------------------------------------------------------------
Solver MathSolverC:default round 0:
In April, Natalia sold 48 clips. In May, she sold half as many, which is 48 / 2 = 24 clips. To find the total number of clips sold in April and May, we add the amounts: 48 (April) + 24 (May) = 72 clips. 

Thus, the total number of clips sold by Natalia is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverB:default round 0:
In April, Natalia sold 48 clips. In May, she sold half as many clips, which is 48 / 2 = 24 clips. To find the total clips sold in April and May, we add both amounts: 

48 (April) + 24 (May) = 72.

Thus, the total number of clips sold altogether is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverD:default round 0:
Natalia sold 48 clips in April. In May, she sold half as many, which is \( \frac{48}{2} = 24 \) clips. To find the total clips sold in both months, we add the clips sold in April and May together:

\[ 48 + 24 = 72 \]

Thus, Natalia sold a total of 72 clips.

The answer is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverC:default round 1:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverA:default round 1:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverA:default round 0:
In April, Natalia sold clips to 48 friends. In May, she sold half as many, which is calculated as follows:

Half of 48 is \( 48 \div 2 = 24 \).

Now, to find the total clips sold in April and May, we add the totals from both months:

\( 48 + 24 = 72 \).

Thus, the total number of clips Natalia sold altogether in April and May is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverD:default round 1:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverB:default round 1:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverC:default round 1:
In April, Natalia sold 48 clips. In May, she sold half as many, which is 48 / 2 = 24 clips. The total number of clips sold in April and May is calculated by adding the two amounts: 48 (April) + 24 (May) = 72 clips. 

Therefore, the answer is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverA:default round 1:
In April, Natalia sold 48 clips. In May, she sold half of that amount, which is 48 / 2 = 24 clips. To find the total clips sold in both months, we sum the clips from April and May: 

48 (April) + 24 (May) = 72.

Thus, Natalia sold a total of {{72}} clips. 

The answer is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverD:default round 2:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverB:default round 2:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverD:default round 1:
Natalia sold 48 clips in April. In May, she sold half of that, which is \( 48 \div 2 = 24 \) clips. To find the total clips sold, we add the clips sold in both months:

\[ 48 + 24 = 72 \]

Therefore, the total number of clips sold by Natalia is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverB:default round 1:
In April, Natalia sold 48 clips. In May, she sold half that amount, which is 48 / 2 = 24 clips. To find the total clips sold in both months, we add the amounts: 

48 (April) + 24 (May) = 72.

Therefore, the total number of clips sold altogether by Natalia is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverA:default round 2:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverC:default round 2:
Received all responses from 2 neighbors.
--------------------------------------------------------------------------------
Solver MathSolverA:default round 2:
In April, Natalia sold 48 clips. In May, she sold half of that amount, which is \( 48 \div 2 = 24 \) clips. To find the total clips sold in both months, we add the amounts from April and May:

\( 48 + 24 = 72 \).

Thus, the total number of clips sold by Natalia is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverC:default round 2:
In April, Natalia sold 48 clips. In May, she sold half of that amount, which is \( 48 \div 2 = 24 \) clips. To find the total number of clips sold in both months, we add the clips sold in April and May: 

48 (April) + 24 (May) = 72. 

Thus, the total number of clips sold altogether by Natalia is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverB:default round 2:
In April, Natalia sold 48 clips. In May, she sold half as many, calculated as \( 48 \div 2 = 24 \) clips. To find the total clips sold over both months, we sum the totals: 

\( 48 (April) + 24 (May) = 72 \).

Therefore, the total number of clips Natalia sold is {{72}}.
--------------------------------------------------------------------------------
Solver MathSolverD:default round 2:
To solve the problem, we know that Natalia sold 48 clips in April. In May, she sold half that amount, which is calculated as \( 48 \div 2 = 24 \) clips. To find the total number of clips sold over both months, we add the two amounts together:

\[ 48 + 24 = 72 \]

Thus, the total number of clips sold by Natalia is {{72}}.
--------------------------------------------------------------------------------
Aggregator MathAggregator:default received all final answers from 4 solvers.
--------------------------------------------------------------------------------
Aggregator MathAggregator:default publishes final answer:
72
benchmark GSM8K
Improving Multi-Agent Debate with Sparse Communication Topology
Tema & Suscripción