Python A2A es una implementación del protocolo de comunicaciones de agente a agente (A2A) de Google, diseñado para estandarizar la interacción entre agentes de inteligencia artificial. Este protocolo aborda un desafío clave en el ecosistema de IA: permitir que diferentes servicios de IA se comuniquen de manera fluida sin necesidad de capas de conversión personalizadas.
El ecosistema de IA se ha especializado, con múltiples servicios que exponen sus propios formatos de API y parámetros. Los desarrolladores inviertne demasiado tiempo en construir infraestructura de comunicación. Python A2A resuelve este problema proporcionando una forma estandarizada para que los agentes se comuniquen entre sí, independientemente de su implementación subyacente.
Instalación y Configuración Inicial
# Instalación base del paquete
pip install python-a2a
# Para integración con OpenAI
pip install "python-a2a[openai]"
# Para integración con Anthropic Claude
pip install "python-a2a[anthropic]"
# Instalación completa con todas las dependencias opcionales
pip install "python-a2a[all]"
Principios Fundamentales del Protocolo
La implementación de Python A2A se basa en varios conceptos centrales:
- Formato de Mensaje Estructurado: Define esquemas para contenido textual, invocaciones de funciones y respuestas.
- Gestión de Hilos Conversacionales: Permite mantener el contexto a través de múltiples interacciones.
- Mecanismo de Invocación de Funciones: Ofrece un método estandarizado para que los agentes expongan y ejecuten funciones.
- Manejo de Errores Consistente: Establece un formato unificado para los mensajes de error.
Creación de un Agente de Ejemplo
Construyamos un agente simple que replica mensajes con un prefijo. Primero, el servidor del agente:
from python_a2a import A2AServer, Message, TextContent, MessageRole, run_server
class AgenteEco(A2AServer):
"""Agente que repite los mensajes recibidos con un prefijo."""
def process_request(self, incoming_msg):
if incoming_msg.content.type == "text":
original_text = incoming_msg.content.text
response_text = f"Reflejo: {original_text}"
return Message(
content=TextContent(text=response_text),
role=MessageRole.AGENT,
parent_message_id=incoming_msg.message_id,
conversation_id=incoming_msg.conversation_id
)
if __name__ == "__main__":
servidor_agente = AgenteEco()
run_server(servidor_agente, host="0.0.0.0", port=5000)
Posteriormente, implementamos un cliente para interactuar con nuestro agente:
from python_a2a import A2AClient, Message, TextContent, MessageRole
cliente_agente = A2AClient("http://localhost:5000/a2a")
mensaje_usuario = Message(
content=TextContent(text="¿Puedes escucharme?"),
role=MessageRole.USER
)
respuesta_del_agente = cliente_agente.send_message(mensaje_usuario)
print(f"El agente respondió: {respuesta_del_agente.content.text}")
Invocación de Funciones entre Agentes
Una característica potente de A2A es la invocación estandarizada de funciones. A continuación, un agente que ofrece operaciones matemáticas:
import math
from python_a2a import (
A2AServer, Message, TextContent, FunctionCallContent,
FunctionResponseContent, FunctionParameter, MessageRole, run_server
)
class ServicioCalculadora(A2AServer):
"""Agente que ofrece funciones de cálculo matemático."""
def process_request(self, solicitud):
if solicitud.content.type == "text":
guia = (
"Soy un servicio de cálculo. Funciones disponibles:\n"
"- operar: Aritmética básica (operando1, operando2, operación)\n"
"- raiz_cuadrada: Calcula la raíz cuadrada (numero)"
)
return Message(
content=TextContent(text=guia),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
elif solicitud.content.type == "function_call":
nombre_funcion = solicitud.content.name
argumentos = {arg.name: arg.value for arg in solicitud.content.parameters}
try:
if nombre_funcion == "operar":
op = argumentos.get("operacion", "sumar")
num1 = float(argumentos.get("operando1", 0))
num2 = float(argumentos.get("operando2", 0))
if op == "sumar":
resultado = num1 + num2
elif op == "restar":
resultado = num1 - num2
elif op == "multiplicar":
resultado = num1 * num2
elif op == "dividir":
if num2 == 0:
raise ArithmeticError("División por cero no definida")
resultado = num1 / num2
else:
raise ValueError(f"Operación no reconocida: {op}")
return Message(
content=FunctionResponseContent(
name=nombre_funcion,
response={"resultado": resultado}
),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
elif nombre_funcion == "raiz_cuadrada":
valor = float(argumentos.get("numero", 0))
if valor < 0:
raise ValueError("Valor debe ser no negativo")
raiz = math.sqrt(valor)
return Message(
content=FunctionResponseContent(
name=nombre_funcion,
response={"resultado": raiz}
),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
except Exception as error:
return Message(
content=FunctionResponseContent(
name=nombre_funcion,
response={"error": str(error)}
),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
if __name__ == "__main__":
mi_servicio = ServicioCalculadora()
run_server(mi_servicio, host="0.0.0.0", port=5001)
Para invocar una función en el agente calculadora, el cliente genera una solicitud específica:
from python_a2a import (
A2AClient, Message, FunctionCallContent,
FunctionParameter, MessageRole
)
cliente_calculadora = A2AClient("http://localhost:5001/a2a")
solicitud_funcion = Message(
content=FunctionCallContent(
name="operar",
parameters=[
FunctionParameter(name="operacion", value="multiplicar"),
FunctionParameter(name="operando1", value=7),
FunctionParameter(name="operando2", value=6)
]
),
role=MessageRole.USER
)
respuesta = cliente_calculadora.send_message(solicitud_funcion)
if respuesta.content.type == "function_response":
valor = respuesta.content.response.get("resultado")
if valor is not None:
print(f"Resultado de la operación: {valor}") # Salida: 42
Agentes Basados en Modelos de Lenguaje Grandes (LLM)
Python A2A incluye integraciones listas para usar con proveedores populares de LLM. Ejemplo con OpenAI:
import os
from python_a2a import OpenAIA2AServer, run_server
agente_openai = OpenAIA2AServer(
api_key=os.environ.get("MI_CLAVE_OPENAI"),
model="gpt-4",
system_prompt="Eres un asistente de IA útil y conciso."
)
if __name__ == "__main__":
run_server(agente_openai, host="0.0.0.0", port=5002)
De manera similar, se puede configurar un agente basado en Anthropic Claude:
import os
from python_a2a import ClaudeA2AServer, run_server
agente_claude = ClaudeA2AServer(
api_key=os.environ.get("MI_CLAVE_ANTHROPIC"),
model="claude-3-opus-20240229",
system_prompt="Responde con precisión y claridad."
)
if __name__ == "__main__":
run_server(agente_claude, host="0.0.0.0", port=5003)
Orquestación de Múltiples Agentes
El verdadero potencial de A2A radica en conectar varios agentes. Implementemos un flujo de trabajo para un asistente de investigación:
from python_a2a import (
A2AClient, Message, TextContent, MessageRole, Conversation
)
def flujo_investigacion(pregunta):
# Conexión a agentes especializados
cliente_llm = A2AClient("http://localhost:5002/a2a")
cliente_busqueda = A2AClient("http://localhost:5003/a2a")
cliente_resumen = A2AClient("http://localhost:5004/a2a")
# Conversación para rastrear el flujo completo
dialogo = Conversation()
dialogo.create_text_message(
text=f"Tema de investigación: {pregunta}",
role=MessageRole.USER
)
# Paso 1: Generación de consultas de búsqueda
solicitud_consultas = Message(
content=TextContent(
text=f"Basado en el tema: '{pregunta}', "
f"genera 3 consultas de búsqueda específicas para encontrar información relevante."
),
role=MessageRole.USER
)
respuesta_consultas = cliente_llm.send_message(solicitud_consultas)
dialogo.add_message(respuesta_consultas)
# Paso 2: Recopilación de información
solicitud_busqueda = Message(
content=TextContent(
text=f"Busca información para responder: {pregunta}\n\n"
f"Utiliza estas consultas:\n{respuesta_consultas.content.text}"
),
role=MessageRole.USER
)
resultados_busqueda = cliente_busqueda.send_message(solicitud_busqueda)
dialogo.add_message(resultados_busqueda)
# Paso 3: Síntesis de la información
solicitud_sintesis = Message(
content=TextContent(
text=f"Sintetiza la siguiente información para responder: '{pregunta}'\n\n"
f"Datos recopilados:\n{resultados_busqueda.content.text}"
),
role=MessageRole.USER
)
resumen_final = cliente_resumen.send_message(solicitud_sintesis)
dialogo.add_message(resumen_final)
# Respuesta final en la conversación
dialogo.create_text_message(
text=f"Respuesta a tu pregunta de investigación:\n\n{resumen_final.content.text}",
role=MessageRole.AGENT
)
return dialogo
if __name__ == "__main__":
tema = input("¿Cuál es tu pregunta de investigación? ")
resultado_dialogo = flujo_investigacion(tema)
print("\nInvestigación completada.")
print("=" * 50)
print(resultado_dialogo.messages[-1].content.text)
Integración de Servicios Heterogéneos
Este ejemplo ilustra cómo A2A simplifica la comunicación entre agentes que proveen servicios diferentes, como el clima y la planificación:
from python_a2a import A2AClient, Message, TextContent, MessageRole
def organizar_viaje(destino):
cliente_clima = A2AClient("http://localhost:5001/a2a")
cliente_planificador = A2AClient("http://localhost:5002/a2a")
# Consulta al agente del clima
msg_clima = Message(
content=TextContent(text=f"¿Cuál es el pronóstico del tiempo para {destino}?"),
role=MessageRole.USER
)
pronostico = cliente_clima.send_message(msg_clima)
# Solicitud al agente planificador con datos del clima
msg_plan = Message(
content=TextContent(
text=f"Estoy planificando un viaje a {destino}. Pronóstico: {pronostico.content.text}"
f" Sugiéreme actividades adecuadas."
),
role=MessageRole.USER
)
sugerencias = cliente_planificador.send_message(msg_plan)
return sugerencias.content.text
Gestión de Conversaciones Multivuelta
La clase Conversation permite administrar interacciones prolongadas, manteniendo el historial de mensajes:
from python_a2a import Conversation, MessageRole, A2AClient
conversacion = Conversation()
conversacion.create_text_message(
text="¿Hace buen tiempo hoy?",
role=MessageRole.USER
)
agente_meteo = A2AClient("http://localhost:5001/a2a")
ultimo_mensaje = conversacion.messages[-1]
respuesta_inicial = agente_meteo.send_message(ultimo_mensaje)
conversacion.add_message(respuesta_inicial)
conversacion.create_text_message(
text="Gracias. ¿Necesitaré un paraguas?",
role=MessageRole.USER
)
respuesta_secundaria = agente_meteo.send_message(
conversacion.messages[-1],
conversation_id=conversacion.conversation_id
)
conversacion.add_message(respuesta_secundaria)
for mensaje in conversacion.messages:
identificador = "Humano" if mensaje.role == MessageRole.USER else "Agente"
print(f"{identificador}: {mensaje.content.text}")
Manejo de Errores Estandarizado
La biblioteca proporciona mecanismos para capturar errores comunes en la comunicación:
from python_a2a import A2AClient, Message, TextContent, MessageRole
cliente = A2AClient("http://localhost:5001/a2a")
try:
paquete = Message(
content=TextContent(text="Saludo de prueba"),
role=MessageRole.USER
)
respuesta = cliente.send_message(paquete)
except ConnectionError as ce:
print(f"Error de conexión con el agente: {ce}")
except TimeoutError as te:
print(f"Tiempo de espera excedido: {te}")
except Exception as ex:
print(f"Error inesperado durante la comunicación: {ex}")
Plantilla para Agentes Personalizados
Para crear un agente compatible con A2A, se puede seguir esta estructura base:
from python_a2a import A2AServer, Message, TextContent, MessageRole, run_server
class MiAgentePersonalizado(A2AServer):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Inicialización de componentes específicos del agente
def process_request(self, solicitud):
"""Punto de entrada principal para el procesamiento de mensajes."""
if solicitud.content.type == "text":
contenido_recibido = solicitud.content.text
respuesta_generada = self.mi_logica_especifica(contenido_recibido)
return Message(
content=TextContent(text=respuesta_generada),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
elif solicitud.content.type == "function_call":
# Lógica para manejar invocaciones de funciones
pass
return Message(
content=TextContent(text="Tipo de mensaje no soportado por este agente."),
role=MessageRole.AGENT,
parent_message_id=solicitud.message_id,
conversation_id=solicitud.conversation_id
)
def mi_logica_especifica(self, texto):
# Implementación de la lógica de negocio única
return f"Procesado: {texto}"
if __name__ == "__main__":
agente = MiAgentePersonalizado()
run_server(agente, host="0.0.0.0", port=5005)