JetBrains AI
Supercharge your tools with AI-powered features inside many JetBrains products
Koog × A2A: creación de agentes de IA conectados en Kotlin
Si alguna vez ha intentado crear un sistema de varios agentes de IA, probablemente se habrá encontrado con este problema. Empieza de forma bastante sencilla: tiene un agente escribiendo artículos de blog, otro corrigiéndolas y quizá un tercero sugiriendo o generando imágenes. Por separado, son eficaces. Pero ¿y si quiere que trabajen juntos? Ahí es donde todo puede comenzar a torcerse.
Cada agente habla su propio «idioma»: uno utiliza una interfaz API diferente, otro tiene su propio formato de mensaje, y todos pueden tener requisitos de autenticación específicos. Hacer que se comuniquen significa escribir código de integración personalizado para cada conexión. En lugar de centrarse en hacer que sus agentes sean más inteligentes, más rápidos o más útiles, usted se atasca construyendo puentes entre ellos.
Qué hace A2A: forma la capa de comunicación entre agentes
Aquí es donde entra en acción el protocolo Agent2Agent (A2A).
Con A2A, sus agentes pueden comunicarse directamente a través de un protocolo estandarizado, que funciona como un traductor universal para su ecosistema de IA. Su agente de redacción pasa sin problemas el contenido a su corrector, que activa su generador de imágenes, mientras que el corrector vuelve con correcciones y el generador de imágenes solicita aclaraciones de estilo. Todo ello, orquestado a través de una única capa de comunicación unificada.
En lugar de gestionar docenas de conexiones punto a punto, A2A le ofrece:
- Conectividad plug-and-play: los agentes se descubren automáticamente y se conectan entre sí.
- Mensajes estandarizados: formato unificado, protocolo claro y ningún problema de traducción.
- Orquestación integrada: defina los flujos de trabajo una vez y deje que A2A se encargue de la coordinación.
- Escalabilidad sin complejidad: añada o reutilice agentes sin reescribir las conexiones existentes.
¿El resultado? Usted dedica tiempo a mejorar las capacidades de sus agentes, y no a depurar sus conversaciones. Y lo mejor: puede implementar sus agentes utilizando cualquier lenguaje o marco de trabajo que desee. Para los usuarios de JVM, Koog es una opción de primer orden y, a partir de la versión 0.5.0, se integra perfectamente con el ecosistema A2A.
Qué hace Koog: el motor interno de orquestación
Koog es un marco basado en Kotlin para crear agentes de IA orientados a JVM, Android, iOS, WebAssembly y aplicaciones dentro del navegador. Destaca en lo siguiente:
- Gestión de flujos de trabajo complejos: diseñe estrategias basadas en grafos con compatibilidad con bucles, ramas, respaldos y ejecución paralela de ramas.
- Componentes listos para usar: saque partido a sus nodos integrados para invocar a LLM y herramientas externas, resumir el historial de mensajes y ejecutar estrategias completas.
- Orquestación de herramientas: convierta cualquier función de su código en una herramienta que su agente de IA pueda utilizar, ya sea de forma secuencial o incluso en paralelo.
- Integración MCP nativa: conéctese con fluidez a cualquier servidor MCP utilizando el SDK de MCP de Kotlin.
- Compatibilidad con memoria y almacenamiento: compatibilidad integrada con la memoria del agente y los flujos de trabajo RAG (generación aumentada por recuperación) con una gestión eficaz del contexto.
- Tolerancia a fallos: reintentos integrados, puntos de control, mecanismos de recuperación y persistencia del estado para garantizar una ejecución fiable.
- Observabilidad: manejo completo de eventos del agente, registro y compatibilidad con OpenTelemetry con integraciones incorporadas con Langfuse y W&B Weave.
En resumen, Koog es estupendo para crear agentes de IA fiables.
Por qué combinar Koog con A2A
Koog y A2A cubren diferentes capas de la pila de agentes de IA. Cuando se utilizan juntos, se complementan y completan las lagunas.
Koog ya se encarga de las partes más difíciles de la orquestación de la IA necesarias para el uso empresarial en el mundo real.
A2A añade la pieza que faltaba: permite a sus agentes Koog comunicarse con cualquier otro agente compatible con A2A de su ecosistema. En lugar de crear integraciones personalizadas para cada servicio externo, sus flujos de trabajo de IA de Koog pueden descubrir y utilizar automáticamente otros agentes.
El resultado es una combinación perfecta: los flujos de trabajo avanzados de Koog se convierten en tareas A2A que cualquier agente puede solicitar, mientras que sus agentes Koog aprovechan toda la potencia del ecosistema A2A. Y puesto que Koog funciona en entornos backend, en el dispositivo y en el navegador, puede ofrecer IA interconectada de forma más extensa y eficaz que nunca.
¿Cómo es posible? ¡Veámoslo!
Protocolo A2A
El protocolo A2A define los elementos esenciales para la comunicación entre agentes:
- Descubrimiento de agentes a través de tarjetas de agente estandarizadas (documentos JSON que describen las capacidades).
- Formatos de mensajes para solicitudes y respuestas con esquemas coherentes.
- Gestión del ciclo de vida de las tareas con estados claros: enviado → en funcionamiento → completado/fallido.
- Capas de transporte como JSON-RPC, gRPC y REST.
- Esquemas de seguridad mediante el estándar OAuth2, claves API y tokens JWT.
- Tratamiento de errores con códigos de error normalizados.

Tarjetas de agente: tarjetas de visita digitales
Cada agente del ecosistema A2A publica sus capacidades a través de una «tarjeta de agente»: un archivo JSON estandarizado alojado en alguna URL, por ejemplo /.well-known/agent-card.json, en el dominio del agente. La tarjeta de agente actúa como una tarjeta de visita digital que permite a otros agentes descubrir los servicios que ofrece.
Una tarjeta de agente suele contener lo siguiente:
- Información básica: como el nombre del agente, la descripción y la versión.
- Habilidades: lo que el agente es capaz de hacer (por ejemplo, redactar borradores de documentos, corregir textos, analizar datos y generar imágenes).
- Puntos de conexión: cómo llegar al agente.
- Otra información opcional: capacidades habilitadas, autenticación y más.
Este mecanismo de descubrimiento elimina la necesidad del trabajo manual de integración. Cuando un agente necesita una habilidad específica, simplemente comprueba la tarjeta de agente correspondiente para saber cómo interactuar con ese servicio.
En Koog, las tarjetas de agente se definen utilizando clases de datos Kotlin:
val agentCard = AgentCard(
name = "Redactor de blogs",
description = "Agente de IA que crea artículos y publicaciones de alta calidad para blogs",
url = "https://api.blog-writer.com/a2a/v1",
version = "1.0.0",
capabilities = AgentCapabilities(streaming = true),
defaultInputModes = listOf("text/plain"),
defaultOutputModes = listOf("text/markdown"),
skills = listOf(
AgentSkill(
id = "write-post",
name = "Redacción de artículos de blog",
description = "Genere publicaciones de blog interesantes sobre cualquier tema",
tags = listOf("writing", "content", "blog"),
examples = listOf("Escriba una publicación sobre tendencias de IA")
)
)
)
Mensajería universal: un patrón sencillo
A2A utiliza un formato de mensaje único y estandarizado para todas las comunicaciones entre agentes. Esta simplicidad es potente: en lugar de aprender un sinfín de API diferentes, los agentes solo necesitan entender un patrón de comunicación.
Todas las interacciones siguen el mismo flujo:
- Enviar un mensaje con la solicitud de tarea y los parámetros.
- Recibir resultados inmediatos o una tarea para su seguimiento.
- Obtener actualizaciones a través de canales en tiempo real para operaciones más prolongadas.
Gracias a este enfoque universal, añadir nuevas capacidades a los agentes no requiere cambiar los protocolos de comunicación. Tanto si pide a un agente que resuma un texto como si debe generar un informe complejo, la estructura del mensaje sigue siendo coherente.
En Koog, crear y enviar un mensaje es sencillo utilizando objetos y protocolos ya implementados:
val message = Message(
role = Role.User,
parts = listOf(
TextPart("Escriba una publicación de blog sobre el futuro de los agente de IA")
),
contextId = "blog-project-456"
)
val request = Request(
data = MessageSendParams(
message = message,
configuration = MessageConfiguration(
blocking = false, // Obtener primera respuesta
historyLength = 5 // Incluir contexto
)
)
)
val response = client.sendMessage(request)
El formato de mensaje admite contenido enriquecido a través de diferentes tipos Part, incluidos TextPart para contenido de texto sin formato, FilePart para archivos adjuntos y DataPart para datos JSON estructurados.
Esta estructura unificada logra que sus agentes Koog puedan comunicarse sin problemas con cualquier agente compatible con A2A, ya sea para el procesamiento de textos, el análisis de archivos o las transformaciones complejas de datos.
Ciclo de vida de las tareas: flujos de trabajo inteligentes
A2A gestiona de forma inteligente diferentes tipos de trabajo en función de su complejidad y duración:
Mensajes inmediatos: las operaciones sencillas como el formateo de texto o los cálculos rápidos muestran los resultados directamente en la respuesta de la IA. Sin esperas, sin necesidad de seguimiento.
Tareas de larga duración: las operaciones complejas como el análisis de documentos o los flujos de trabajo de varios pasos se programan y devuelven una tarea. A continuación, el agente solicitante puede supervisar el progreso y recuperar los resultados de la tarea una vez que estén listos.
Actualizaciones en tiempo real: para las operaciones que requieren mucho tiempo, los eventos enviados por el servidor (SSE) presentan actualizaciones del progreso en tiempo real. Esto mantiene informados a los agentes sin necesidad de sondeos constantes.
class BlogWriterExecutor : AgentExecutor {
override suspend fun execute(
context: RequestContext,
eventProcessor: SessionEventProcessor
) {
val task = Task(
contextId = context.contextId,
status = TaskStatus(
state = TaskState.Submitted,
message = Message(
role = Role.Agent,
parts = listOf(TextPart("Solicitud de redacción de blog recibida")),
contextId = context.contextId,
taskId = context.taskId,
)
)
)
eventProcessor.sendTaskEvent(task)
...
}
}
Seguridad integrada: solo estándares del sector
A2A no reinventa la seguridad. En su lugar, se basa en estándares demostrados y ampliamente adoptados como OAuth2, claves API y HTTPS estándar.
Esto significa que los desarrolladores no tienen que aprender nuevos esquemas de autenticación. Si entiende la seguridad de las API web modernas, ya entiende la seguridad A2A. El sistema hereda todas las herramientas, las mejores prácticas y las auditorías de seguridad que acompañan a estas normas establecidas.
"securitySchemes": {
"google": {
"openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration",
"type": "openIdConnect"
}
}
class AuthorizedA2AServer(
agentExecutor: AgentExecutor,
agentCard: AgentCard,
agentCardExtended: AgentCard? = null,
taskStorage: TaskStorage = InMemoryTaskStorage(),
messageStorage: MessageStorage = InMemoryMessageStorage(),
private val authService: AuthService, // Servicio responsable de la autenticación
) : A2AServer(
agentExecutor = agentExecutor,
agentCard = agentCard,
agentCardExtended = agentCardExtended,
taskStorage = taskStorage,
messageStorage = messageStorage,
) {
private suspend fun authenticateAndAuthorize(
ctx: ServerCallContext,
requiredPermission: String
): AuthenticatedUser {
val token = ctx.headers["Authorization"]?.firstOrNull()
?: throw A2AInvalidParamsException("Falta token de autorización")
val user = authService.authenticate(token)
?: throw A2AInvalidParamsException("Token de autorización no válido")
if (requiredPermission !in user.permissions) {
throw A2AUnsupportedOperationException("Permisos insuficientes")
}
return user
}
override suspend fun onSendMessage(
request: Request,
ctx: ServerCallContext
): Response {
val user = authenticateAndAuthorize(ctx, requiredPermission = "send_message")
// Pasar datos de usuario al ejecutor del agente a través del estado de contexto
val enrichedCtx = ctx.copy(
state = ctx.state + (AuthStateKeys.USER to user)
)
// Delegar a implementación superior con contexto enriquecido
return super.onSendMessage(request, enrichedCtx)
}
// el resto de los métodos incorporados de A2A
// ...
}
Cómo integrar los agentes Koog con A2A
El marco Koog incluye tanto el cliente como el servidor A2A integrados. Esto significa que sus agentes Koog pueden comunicarse sin problema con otros agentes compatibles con A2A a la vez que se hacen visibles para el mundo exterior. He aquí un sencillo ejemplo que demuestra cómo puede ponerlo en práctica.
Cómo integrar agentes Koog en servidores A2A
En primer lugar, defina una estrategia para el agente. Koog ofrece prácticos conversores (toKoogMessage, toA2AMessage) para transformar sin problemas entre los formatos de mensajes Koog y A2A, lo que elimina la necesidad de la serialización manual. Los nodos especializados como nodeA2ASendMessage se encargan del proceso de intercambio de mensajes, lo que hace que los flujos de trabajo de comunicación sean sencillos de implementar:
fun blogpostWritingStrategy() = strategy("blogpost-writer-strategy") {
val blogpostRequest by node { input ->
val userMessage = input.toKoogMessage().content
llm.writeSession {
user {
+"Escriba un artículo de blog basado en la solicitud del usuario"
+xml {
tag("user_request") {
+userMessage
}
}
}
requestLLM().toA2AMessage()
}
}
val sendMessage by nodeA2ARespondMessage()
nodeStart then blogpostRequest then sendMessage then nodeFinish
}
En segundo lugar, defina el propio agente. Una vez instalada la funcionalidad A2AServer, su agente pasa a ser detectable y accesible para otros en el ecosistema, lo que permite la creación de redes sofisticadas en las que agentes especializados colaboran sin problemas.
fun createBlogpostWritingAgent(
requestContext: RequestContext,
eventProcessor: SessionEventProcessor
): AIAgent {
// Obtener mensajes existentes para el contexto actual de la conversación
val messageHistory = requestContext.messageStorage.getAll().map { it.toKoogMessage() }
val agentConfig = AIAgentConfig(
prompt = prompt("blogpost") {
system("Usted es un agente encargado de redactar artículos para blogs")
messages(messageHistory)
},
model = GoogleModels.Gemini2_5Flash,
maxAgentIterations = 5
)
return agent = AIAgent(
promptExecutor = MultiLLMPromptExecutor(
LLMProvider.Google to GoogleLLMClient(System.getenv("GOOGLE_API_KEY")),
),
strategy = blogpostWritingStrategy(),
agentConfig = agentConfig
) {
install(A2AAgentServer) {
this.context = requestContext
this.eventProcessor = eventProcessor
}
handleEvents {
onAgentFinished { ctx ->
// Actualice el contexto de la conversación actual con la respuesta del agente
val resultMessge = ctx.result as A2AMessage
requestContext.messageStorage.save(resultMessge)
}
}
}
}
En tercer lugar, tenemos que incorporar al agente en el ejecutor y luego definir un servidor.
class BlogpostAgentExecutor : AgentExecutor {
override suspend fun execute(
context: RequestContext,
eventProcessor: SessionEventProcessor
) {
createBlogpostWritingAgent(context, eventProcessor)
.run(context.params.message)
}
}
val a2aServer = A2AServer(
agentExecutor = BlogpostAgentExecutor(),
agentCard = agentCard,
)
El último paso consiste en definir un transporte de servidor y ejecutar el servidor.
val transport = HttpJSONRPCServerTransport(
requestHandler = a2aServer
)
transport.start(
engineFactory = Netty,
port = 8080,
path = "/a2a",
wait = true,
agentCard = agentCard,
agentCardPath = A2AConsts.AGENT_CARD_WELL_KNOWN_PATH
)
¡Su agente ya está listo para atender solicitudes!
Cómo llamar a otros agentes compatibles con A2A desde un agente Koog
En primer lugar, debe configurar un cliente A2A y conectarlo para obtener una tarjeta de agente.
val agentUrl = "https://example.com"
val cardResolver = UrlAgentCardResolver(
baseUrl = agentUrl,
path = A2AConsts.AGENT_CARD_WELL_KNOWN_PATH,
)
val transport = HttpJSONRPCClientTransport(
url = agentUrl,
)
val a2aClient = A2AClient(
transport = transport,
agentCardResolver = cardResolver
)
// Inicialice el cliente y obtenga la tarjeta
a2aClient.connect()
A continuación, puede utilizar nodeA2ASendMessage o nodeA2ASendMessageStreaming en su estrategia para llamar a estos clientes y recibir un mensaje o una respuesta de tarea.
val agentId = "agent_id"
val agent = AIAgent(
promptExecutor = MultiLLMPromptExecutor(
LLMProvider.Google to GoogleLLMClient(System.getenv("GOOGLE_API_KEY")),
),
strategy = strategy("a2a") {
val nodePrepareRequest by node<String, A2AClientRequest> { input ->
A2AClientRequest(
agentId = agentId,
callContext = ClientCallContext.Default,
params = MessageSendParams(
message = A2AMessage(
messageId = Uuid.random().toString(),
role = Role.User,
parts = listOf(
TextPart(input)
)
)
)
)
}
val nodeA2A by nodeA2AClientSendMessage(agentId)
val nodeProcessResponse by node {
// Process event
when (it) {
is A2AMessage -> it.parts
.filterIsInstance()
.joinToString(separator = "n") { it.text }
is Task -> it.artifacts
.orEmpty()
.flatMap { it.parts }
.filterIsInstance()
.joinToString(separator = "n") { it.text }
}
}
nodeStart then nodePrepareRequest then nodeA2A then nodeProcessResponse then nodeFinish
},
agentConfig = agentConfig
) {
install(A2AAgentClient) {
this.a2aClients = mapOf(agentId to client)
}
}
agent.run("Escriba un artículo de blog sobre la integración de A2A y Koog")
Próximos pasos
Para profundizar en Koog y A2A, consulte estos útiles materiales:
Artículo original en inglés de: