API Gateway en Go que recibe requests de n8n y enruta al agente IA especializado segun la modalidad del negocio. Implementa circuit breaker por agente, metricas Prometheus, health checks paralelos y registro dinamico de agentes.
┌──────────┐ ┌───────────────────────────┐ ┌──────────────────┐
│ │ POST │ MaravIA Gateway │ │ Agente Venta │
│ n8n │──────>│ │──CB──>│ :8001/api/chat │
│ │ │ /api/agent/chat │ └──────────────────┘
└──────────┘ │ │ ┌──────────────────┐
│ Routing por modalidad: │──CB──>│ Agente Cita │
│ citas -> cita │ │ :8002/api/chat │
│ ventas -> venta │ └──────────────────┘
│ reservas -> reserva │ ┌──────────────────┐
│ citas y ventas │──CB──>│ Agente Reserva │
│ -> citas_ventas│ │ :8003/api/chat │
│ │ └──────────────────┘
│ CB = Circuit Breaker │ ┌──────────────────┐
│ │──CB──>│ Agente Citas y │
└───────────────────────────┘ │ Ventas :8004 │
└──────────────────┘
n8n ──POST──> Gateway ──POST──> Agente Python (FastAPI + LangGraph)
│
n8n <──JSON── Gateway <──JSON─────────┘
- n8n envia
{message, session_id, config}al gateway - Gateway lee
config.modalidady selecciona el agente - Gateway reenvia al agente con
{message, session_id, context} - Agente responde
{reply, url} - Gateway responde a n8n con
{reply, session_id, agent_used, url}
| Componente | Tecnologia |
|---|---|
| Lenguaje | Go 1.26+ |
| Router | Chi v5 |
| Config | cleanenv (env -> struct) |
| Logging | log/slog (stdlib, JSON estructurado) |
| Metricas | Prometheus client_golang |
| Circuit Breaker | gobreaker v2 (por agente) |
| HTTP Client | net/http.Client (connection pooling, transport tuneado) |
cd gateway
cp .env.example .env
go mod tidy
go run ./cmd/gatewaygo build -o gateway ./cmd/gateway
./gatewaydocker build -t maravia-gateway .
docker run -p 8000:8000 --env-file .env maravia-gateway
# O con Docker Compose
docker compose up --buildImagen Docker: multi-stage build (golang:1.26-alpine -> alpine:3.19), binario estatico, usuario no-root (appuser UID 10001). ~20-30 MB.
gateway/
├── cmd/gateway/
│ └── main.go # Entry point, wiring, graceful shutdown
├── internal/
│ ├── agent/ # Registro y routing de agentes
│ │ ├── registry.go # Registry: escanea AGENT_*_URL del env (dinamico)
│ │ └── routing.go # ModalidadToAgent: mapea modalidad -> agente
│ ├── config/
│ │ └── config.go # Config del servidor (puertos, timeouts, CORS)
│ ├── domain/
│ │ └── flex.go # FlexBool, FlexInt (tipos flexibles para n8n)
│ ├── handler/
│ │ ├── chat.go # POST /api/agent/chat (interfaz AgentCaller)
│ │ └── health.go # GET /health (paralelo, interfaz AgentLister)
│ ├── metrics/
│ │ └── metrics.go # Prometheus: counters + histogramas
│ ├── middleware/
│ │ ├── cors.go # CORS configurable
│ │ └── logger.go # Request logging (method, path, status, duration)
│ └── proxy/
│ └── agents.go # HTTP client + circuit breaker por agente
├── .env.example
├── Dockerfile # Multi-stage build (alpine, non-root)
├── compose.yaml
├── go.mod
└── go.sum
| Paquete | Responsabilidad |
|---|---|
agent |
Registro dinamico de agentes desde env vars + routing por modalidad |
config |
Configuracion del servidor HTTP (sin logica de agentes) |
domain |
Tipos compartidos: FlexBool, FlexInt, Preview() |
handler |
Handlers HTTP. Definen interfaces que consumen (AgentCaller, AgentLister) |
metrics |
Definicion de metricas Prometheus |
middleware |
CORS y logging de requests |
proxy |
Cliente HTTP hacia agentes con circuit breaker |
main.go
├── config (carga env vars del servidor)
├── agent (registry + routing)
├── handler (chat, health)
│ ├── domain (FlexBool, FlexInt)
│ └── metrics
├── middleware (cors, logger)
└── proxy (invoker con circuit breaker)
├── agent (registry para URLs/enabled)
└── domain (Preview)
// handler/chat.go — lo que el handler necesita del proxy
type AgentCaller interface {
InvokeAgent(ctx, agent, message string, sessionID int, contextMap map[string]interface{}) (reply string, url *string, err error)
}
// handler/health.go — lo que el health check necesita del registry
type AgentLister interface {
All() []agent.AgentInfo
}
// agent/routing.go — tipo para la funcion de routing
type RouteFunc func(modalidad string) (agentKey string)Los agentes se registran automaticamente escaneando variables de entorno con el patron AGENT_*_URL:
AGENT_VENTA_URL=http://localhost:8001/api/chat
AGENT_CITA_URL=http://localhost:8002/api/chat
AGENT_SOPORTE_URL=http://localhost:8005/api/chat # agregar agente = solo estoAgregar un nuevo agente = solo agregar AGENT_<KEY>_URL al .env. Sin tocar codigo.
Para cada AGENT_<KEY>_URL, el registry busca opcionalmente AGENT_<KEY>_ENABLED (default true). Tambien deriva automaticamente la URL de health (/health).
El campo config.modalidad del request determina el agente. Se normaliza con trim + lowercase:
| Modalidad (n8n) | Agente | Variable de entorno |
|---|---|---|
Citas |
cita |
AGENT_CITA_URL |
Ventas |
venta |
AGENT_VENTA_URL |
Reservas |
reserva |
AGENT_RESERVA_URL |
Citas y Ventas |
citas_ventas |
AGENT_CITAS_VENTAS_URL |
| (otro/fallback) | cita |
AGENT_CITA_URL |
El routing es por valor exacto. No usa LLM.
{"service": "MaravIA Gateway", "status": "running", "endpoints": {"/api/agent/chat": "POST", "/health": "GET", "/metrics": "GET"}}Recibe el request de n8n, enruta al agente por modalidad, devuelve la respuesta.
Request:
{
"message": "Quiero agendar una cita para manana",
"session_id": 3796,
"config": {
"nombre_bot": "MaravIA",
"id_empresa": 1,
"modalidad": "Citas",
"frase_saludo": "Hola! En que puedo ayudarte?",
"archivo_saludo": "",
"personalidad": "amigable y profesional",
"frase_des": "Fue un gusto ayudarte",
"frase_no_sabe": "No tengo esa informacion",
"correo_usuario": "usuario@ejemplo.com",
"duracion_cita_minutos": 30,
"slots": 5,
"agendar_usuario": true,
"agendar_sucursal": false,
"id_prospecto": 3796,
"usuario_id": 42,
"id_chatbot": 1
}
}Response exitosa (200):
{
"reply": "Claro, te ayudare a agendar tu cita. Que dia te conviene?",
"session_id": 3796,
"agent_used": "cita",
"url": null
}Response en error de agente (200): el gateway responde 200 con mensaje fallback para que n8n no rompa el flujo.
{
"reply": "No pude conectar con el agente. Intenta de nuevo en un momento.",
"session_id": 3796,
"agent_used": "cita",
"url": null
}Errores de validacion:
| Status | Causa |
|---|---|
| 400 | JSON invalido, message vacio, session_id negativo, config.id_empresa <= 0 |
| 405 | Metodo distinto a POST |
| 413 | Body mayor a 512 KB |
Verifica el gateway y cada agente habilitado en paralelo (sync.WaitGroup). Timeout 2s por agente.
Todo OK (200):
{
"status": "ok",
"service": "gateway",
"agents": {"cita": "ok", "citas_ventas": "ok", "reserva": "ok", "venta": "ok"}
}Degradado (503):
{
"status": "degraded",
"service": "gateway",
"agents": {"cita": "unreachable", "citas_ventas": "ok", "reserva": "disabled", "venta": "ok"}
}| Estado agente | Significado |
|---|---|
ok |
Respondio con 2xx |
unreachable |
No responde o timeout |
disabled |
Deshabilitado via AGENT_<KEY>_ENABLED=false |
no_url |
Sin URL configurada |
gateway_requests_total{agent, status}— Contador por agente y resultado (ok/error)gateway_request_duration_seconds{agent}— Histograma de latencia por agente
Cada agente tiene su propio circuit breaker (gobreaker v2):
| Parametro | Valor |
|---|---|
| Umbral de apertura | 5 fallos consecutivos |
| Intervalo de evaluacion | 60s |
| Timeout en estado abierto | 60s |
| Max requests en half-open | 3 |
Closed ──(5 fallos)──> Open ──(60s)──> Half-Open ──(exito)──> Closed
│
(fallo)
v
Open
Los cambios de estado se registran en logs.
| Variable | Default | Descripcion |
|---|---|---|
GATEWAY_HTTP_PORT |
8000 |
Puerto HTTP |
GATEWAY_READ_HEADER_TIMEOUT_SEC |
10 |
Timeout lectura de headers (mitiga slowloris) |
GATEWAY_READ_TIMEOUT_SEC |
40 |
Timeout lectura completa (headers + body) |
GATEWAY_WRITE_TIMEOUT_SEC |
35 |
Timeout escritura de respuesta. Debe ser > AGENT_TIMEOUT + 5s |
GATEWAY_IDLE_TIMEOUT_SEC |
60 |
Timeout conexiones keep-alive idle (0 = desactivado) |
CORS_ALLOWED_ORIGINS |
* |
Origenes permitidos (comma-separated) |
LOG_LEVEL |
info |
Nivel de log: debug, info, warn, error |
Los agentes se detectan automaticamente por patron AGENT_<KEY>_URL:
| Variable | Default | Descripcion |
|---|---|---|
AGENT_<KEY>_URL |
— | URL del endpoint del agente. Agregar = registrar agente |
AGENT_<KEY>_ENABLED |
true |
Habilitar/deshabilitar agente |
AGENT_TIMEOUT |
25 |
Timeout HTTP para llamadas a agentes (segundos) |
Ejemplo con 4 agentes:
AGENT_VENTA_URL=http://localhost:8001/api/chat
AGENT_CITA_URL=http://localhost:8002/api/chat
AGENT_RESERVA_URL=http://localhost:8003/api/chat
AGENT_CITAS_VENTAS_URL=http://localhost:8004/api/chat
AGENT_VENTA_ENABLED=true
AGENT_CITA_ENABLED=true
AGENT_RESERVA_ENABLED=true
AGENT_CITAS_VENTAS_ENABLED=true
AGENT_TIMEOUT=25Cada agente backend debe exponer:
POST <agent_url> — Content-Type: application/json
Body recibido:
{
"message": "texto del usuario",
"session_id": 3796,
"context": {
"config": {
"nombre_bot": "MaravIA",
"id_empresa": 1,
"modalidad": "Citas",
"personalidad": "amigable",
"..."
}
}
}Respuesta esperada (200):
{"reply": "respuesta del agente", "url": null}Health check: GET /health retornando 2xx.
- Limite de body: 512 KB por request (previene DoS)
- Timeouts HTTP: ReadHeader, Read, Write, Idle (mitiga slowloris y conexiones colgadas)
- Circuit breaker: Aislamiento de fallos por agente
- CORS configurable: Origenes restringidos en produccion
- Container no-root: Ejecuta como
appuser(UID 10001) - Binario estatico: Sin dependencias de runtime en el container
- Validacion de input: campos requeridos, tipos, limites
El gateway usa un http.Client compartido con transport tuneado:
| Parametro | Valor |
|---|---|
MaxConnsPerHost |
25 |
MaxIdleConnsPerHost |
10 |
MaxIdleConns |
50 |
DialTimeout |
5s |
KeepAlive |
30s |
TLSHandshakeTimeout |
5s |
ResponseHeaderTimeout |
20s |
IdleConnTimeout |
90s |