← Volver al índice | Análisis LLM Departamental | Viabilidad Pekko→Dapr
Plataforma MLOps y Workflows Agénticos¶
Tipo: Arquitectura — Sistema Agéntico
Audiencia: Equipo de desarrollo, dirección técnica, arquitectos
Fecha: 20 de marzo de 2026
Relacionado con: Análisis LLM Departamental | Viabilidad Pekko→Dapr | Gobernanza de Datasets
1. El Problema: De «4 Modelos Genéricos» a «9+ Modelos Especializados»¶
El análisis LLM departamental identificó que cada departamento del IEO necesita su propio stack de IA. Esto genera un problema que no existía con el stack original de un único LLaVA genérico:
| Problema | Descripción |
|---|---|
| ¿Cómo se despliegan 9+ modelos? | Ya no es «instalar Ollama y listo» — hay que gestionar versiones, pesos, configuraciones, actualizaciones |
| ¿Cómo se entrenan? | Fine-tuning con LoRA/QLoRA requiere datasets curados, pipelines de entrenamiento, validación, rollback |
| ¿Cómo se orquestan? | Una consulta puede necesitar YOLO → CLIP → Qwen2.5-VL en cadena, con decisiones intermedias |
| ¿Quién decide qué modelo usar? | Un «agente» inteligente que enrute la consulta al modelo correcto según el departamento y el tipo |
| ¿Cómo se monitoriza? | Precisión en producción, latencia, drift del modelo, uso de VRAM |
[!IMPORTANT] La respuesta a todas estas preguntas es una arquitectura de MLOps + Workflows Agénticos, implementada completamente con Quarkus + Dapr + LangChain4j. Es la capa de orquestación que gobierna toda la IA de la plataforma.
2. Las Tres Capas de la Arquitectura IA¶
flowchart TB
subgraph L1 ["Capa 1 — MLOps"]
REG["MLflow - Registro de modelos"]
DVC2["DVC - Versionado de datasets"]
OLL["Ollama - Servidor de modelos"]
end
subgraph L2 ["Capa 2 — Orquestación"]
DAPR["Dapr Workflow - FSMs y pipelines"]
LC4J["LangChain4j - Agentes IA"]
BIND["Dapr Bindings - Triggers externos"]
end
subgraph L3 ["Capa 3 — Inferencia"]
VLM["Qwen2.5-VL - Visión"]
YOLO3["YOLOv11 - Detección"]
UNET2["U-Net - Segmentación"]
OCE2["OceanGPT - Texto"]
end
REG --> OLL
DVC2 --> REG
BIND --> DAPR
DAPR --> LC4J
LC4J --> L3
style L1 fill:#9b59b6,color:#fff
style L2 fill:#e74c3c,color:#fff
style L3 fill:#2ecc71,color:#fff
2.1 Capa 1 — MLOps: Ciclo de vida de los modelos¶
¿Qué es MLOps? — Es para modelos de IA lo que DevOps es para código: versionado, testing, despliegue, monitorización y rollback de modelos.
| Herramienta | Función | Self-hosted | Licencia |
|---|---|---|---|
| MLflow | Registro de modelos: versiones, stages (Staging/Production/Archived), métricas | ✅ Docker | Apache 2.0 |
| DVC | Versionado de datasets y artefactos grandes (imágenes, pesos) sin inflar Git | ✅ CLI + MinIO | Apache 2.0 |
| Ollama | Servidor de modelos LLM/VLM con Modelfiles (similar a Dockerfile) | ✅ Docker | MIT |
MLflow trackea cada experimento de fine-tuning:
Experimento: LoRA Otolitos v3
├── Run 1: lr=1e-4, epochs=10 → precisión 78% → REJECTED
├── Run 2: lr=5e-5, epochs=20 → precisión 84% → STAGING
└── Run 3: lr=5e-5, epochs=30, aug=True → precisión 89% → PRODUCTION ✅
2.2 Capa 2 — Orquestación: Quarkus + Dapr + LangChain4j¶
| Componente | Función | Tipo |
|---|---|---|
| Dapr Workflow | Orquestación de procesos largos con estado persistente (FSMs, pipelines ETL, entrenamiento) | Declarativo + código |
| Dapr Bindings | Triggers externos: webhooks SharePoint, polling GSheets, MQTT IoT, Copernicus API | Declarativo YAML |
| Dapr Pub/Sub | Eventos desacoplados entre servicios: muestra.nueva, esquema.cambio, modelo.actualizado |
Declarativo YAML |
| LangChain4j | Agentes IA con tool calling autónomo, multi-modelo, RAG | Código Java |
| Quarkus Scheduler | Crons y tareas periódicas: polling, resúmenes, monitorización | Código Java |
[!NOTE] Todo lo que normalmente requeriría una herramienta visual de workflows tipo n8n/Airflow se implementa aquí directamente en Dapr + Quarkus. La ventaja: cero dependencias externas adicionales, todo es parte del mismo stack que ya usamos para el backend.
3. El Cerebro Agéntico: LangChain4j¶
LangChain4j es lo que convierte a Quarkus en un sistema agéntico. Un «agente» es un LLM que puede:
- Recibir una consulta en lenguaje natural
- Decidir qué herramientas usar (tool calling)
- Ejecutar las herramientas
- Razonar sobre los resultados
- Iterar si necesita más información
- Responder con una respuesta fundamentada
3.1 Definición de un Agente¶
// Agente IA del IEO — interfaz CDI declarativa
@RegisterAiService(modelName = "qwen25-vl")
@SystemMessage("""
Eres un experto en biología marina del IEO de Málaga.
Tienes acceso a las siguientes herramientas:
- buscar_especie: busca en FishBase/GBIF
- buscar_otolitos: busca en ChromaDB por similitud visual
- identificar_imagen: usa YOLOv11 para detectar especies
Siempre cita las fuentes de tus respuestas.
""")
public interface AgenteIEO {
String consultar(@UserMessage String consulta,
@ImageUrl String imagen);
}
3.2 Herramientas del Agente (Tools)¶
// Herramientas que el agente puede llamar AUTÓNOMAMENTE
@Tool("Busca especies en la base de datos FishBase")
public List<Especie> buscarEspecie(String nombre) {
return fishBaseClient.search(nombre);
}
@Tool("Busca otolitos similares en ChromaDB por embedding visual")
public List<Otolito> buscarOtolitos(byte[] imagenEmbedding) {
return chromaClient.query(imagenEmbedding, 5);
}
@Tool("Detecta especies en una imagen usando YOLOv11")
public List<Deteccion> identificarImagen(String imagenUrl) {
return yoloClient.detect(imagenUrl);
}
@Tool("Consulta datos oceanográficos de Copernicus")
public DatosOceanograficos consultarCopernicus(String zona, String fecha) {
return copernicusClient.query(zona, fecha);
}
[!IMPORTANT] El agente no está programado para llamar a FishBase primero y luego a ChromaDB. El LLM decide autónomamente qué herramientas usar según la consulta del investigador. Esto es lo que significa «agéntico».
3.3 Agentes por Departamento¶
| Departamento | Agente | Modelo Base | Tools Disponibles |
|---|---|---|---|
| Pesquerías | AgentePesquerias |
Qwen2.5-VL 7B | YOLOv11, U-Net otolitos, FishBase, SIRENO, ChromaDB |
| Acuicultura | AgenteAcuicultura |
Qwen2.5-VL 7B | ResNet-ViT patologías, IoT sensores, historial alimentación |
| Medio Marino | AgenteMedioMarino |
InternVL2.5 8B | MariNeXt, OceanGPT, Copernicus, CTD series |
| Tortugas y Cetáceos | AgenteCetaceos |
Qwen2.5-VL 7B | Happywhale matching, MYDAS, SpeciesNet |
| Oceanografía | AgenteOceanografia |
OceanGPT | Copernicus, Argo, series temporales |
El router de agentes selecciona automáticamente cuál usar según el departamento del usuario logueado y el tipo de consulta:
// Router de agentes — selección dinámica
@ApplicationScoped
public class RouterAgentes {
@Inject @Named("pesquerias") AgenteIEO agentePesquerias;
@Inject @Named("acuicultura") AgenteIEO agenteAcuicultura;
@Inject @Named("medio-marino") AgenteIEO agenteMedioMarino;
public String consultar(String departamento, String consulta, String imagen) {
return switch (departamento) {
case "Pesquerías" -> agentePesquerias.consultar(consulta, imagen);
case "Acuicultura" -> agenteAcuicultura.consultar(consulta, imagen);
case "Medio Marino" -> agenteMedioMarino.consultar(consulta, imagen);
default -> agenteGeneral.consultar(consulta, imagen);
};
}
}
4. Orquestación con Dapr Workflow — Ejemplo Completo¶
Escenario: Un investigador sube una foto de otolito¶
sequenceDiagram
actor INV as Investigador
participant BIND as Dapr Binding
participant WF as Dapr Workflow
participant LC as LangChain4j
participant YOLO4 as YOLOv11
participant UNET3 as U-Net
participant VLM2 as Qwen2.5-VL
participant DB2 as PostgreSQL
INV->>BIND: Sube foto a SharePoint
Note over BIND: Input Binding Graph API
BIND->>WF: Evento muestra.nueva
Note over WF: FSM: Capturada
WF->>YOLO4: Activity: detectar especie
YOLO4-->>WF: Sardina (92%)
Note over WF: FSM: Preprocesando
WF->>UNET3: Activity: segmentar annuli
UNET3-->>WF: 4 anillos detectados
Note over WF: FSM: IdentificandoIA
WF->>LC: Activity: generar informe
LC->>VLM2: Prompt + imagen + datos
VLM2-->>LC: Informe contextualizado
LC-->>WF: Resultado completo
Note over WF: FSM: PendienteConfirmación
WF->>DB2: Persistir resultado
WF->>INV: Pub/Sub notificación
| Paso | Componente Dapr | Equivalente Pekko | Mejora |
|---|---|---|---|
| 1. Detectar cambio en SharePoint | Dapr Input Binding (Graph API webhook) | Actor custom con polling | Declarativo YAML, sin código |
| 2. Iniciar pipeline | Dapr Workflow | Actor supervisor + mensajes | Estado persiste entre reinicios |
| 3. Detectar especie | Workflow Activity → YOLOv11 | Actor AIInferenceActor |
15 líneas vs 200 líneas |
| 4. Segmentar otolito | Workflow Activity → U-Net | Actor custom | Retry automático con compensación |
| 5. Generar informe | LangChain4j (agente con tools) | Actor + código imperativo | Tool calling autónomo |
| 6. Notificar | Dapr Pub/Sub → muestra.confirmada |
Actor + Pekko Streams | Desacoplado, zero código |
Implementación del Workflow en Java¶
// Dapr Workflow — Pipeline de identificación de muestra
@Workflow(name = "IdentificacionMuestraWorkflow")
public class IdentificacionMuestra extends DaprWorkflow {
@Override
public WorkflowStub create() {
return ctx -> {
// 1. Preprocesar imagen
var imagen = ctx.callActivity("preprocesarImagen", input, ImagenLimpia.class);
// 2. Detectar especie con YOLOv11
var deteccion = ctx.callActivity("detectarEspecie", imagen, Deteccion.class);
// 3. Si es otolito → segmentar annuli
if (deteccion.tipo().equals("otolito")) {
var edad = ctx.callActivity("segmentarOtolito", imagen, EdadEstimada.class);
ctx.setState("edad", edad);
}
// 4. Generar informe con agente LangChain4j
var informe = ctx.callActivity("generarInforme", deteccion, Informe.class);
// 5. Esperar confirmación humana (puede tardar horas/días)
var confirmacion = ctx.waitForExternalEvent("confirmacion", Confirmacion.class);
// 6. Persistir resultado final
ctx.callActivity("persistirResultado", confirmacion, Void.class);
};
}
}
[!TIP] La diferencia clave con Pekko: si el servidor se reinicia en el paso 5 (esperando confirmación humana), Dapr Workflow recupera automáticamente el estado y continúa donde se quedó. Con Pekko, habría que implementar Pekko Persistence manualmente (~300 líneas extra).
5. Pipelines ETL — Configuración Estática vs Dinámica¶
5.1 Dos Niveles de Configuración¶
[!IMPORTANT] Los YAML de Dapr definen qué tipos de conexión existen (infraestructura). Las fuentes de datos concretas que cada departamento quiere monitorizar se gestionan dinámicamente desde la UI, sin tocar YAML ni hacer commits.
| Nivel | Qué define | Dónde vive | Quién lo cambia | Frecuencia |
|---|---|---|---|---|
| Infraestructura (YAML) | «La plataforma puede conectarse a SharePoint, GSheets, MQTT» | Git + despliegue Docker | Equipo de desarrollo | Raramente (instalar un driver nuevo) |
| Fuentes de datos (BBDD) | «Monitorizar la carpeta ECOMED/2025/Bio del SharePoint de Pesquerías» | PostgreSQL + UI de administración | Responsable de Departamento o Admin/TI | Frecuentemente (nueva campaña, nuevo GSheet) |
5.2 Nivel 1 — YAML de Infraestructura (se despliega una vez)¶
Estos YAML son equivalentes a «instalar un driver de base de datos» — se configuran al montar la plataforma y no se tocan más:
# dapr/components/graph-api-binding.yaml
# Se despliega UNA VEZ — define que la plataforma PUEDE hablar con SharePoint
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: microsoft-graph
spec:
type: bindings.microsoft.graph
metadata:
- name: tenantId
value: "${AZURE_TENANT_ID}"
- name: clientId
value: "${AZURE_CLIENT_ID}"
# dapr/components/mqtt-binding.yaml
# Se despliega UNA VEZ — define que la plataforma PUEDE recibir datos MQTT
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: iot-sensors
spec:
type: bindings.mqtt3
metadata:
- name: url
value: "${MQTT_BROKER_URL}"
5.3 Nivel 2 — Fuentes de Datos Dinámicas (UI del Admin)¶
Las fuentes concretas se gestionan desde la interfaz web, almacenadas en PostgreSQL:
// Entidad JPA — cada fila es una fuente de datos monitorizada
@Entity
@Table(name = "fuente_datos")
public class FuenteDatos {
@Id UUID id;
String nombre; // "Biometrías ECOMED 2025"
String tipo; // "sharepoint" | "gsheet" | "mqtt" | "oracle_cdc"
String departamentoId; // FK → departamento
String configuracion; // JSON: {path, sheetId, topic, ...}
String periodicidad; // "15min" | "1h" | "diario" | "tiempo_real"
boolean activa; // pausar/activar sin reiniciar
String esquemaEsperado; // JSON Schema para validación
LocalDateTime ultimaEjecucion;
String responsableId; // FK → usuario que la creó
}
// Quarkus @Scheduled — lee fuentes activas de la BBDD, no del YAML
@ApplicationScoped
public class PollingService {
@Inject FuenteDatosRepository fuentesRepo;
@Inject DaprClient dapr;
@Scheduled(every = "1m")
void checkFuentesActivas() {
var fuentes = fuentesRepo.findActivasPendientes();
for (var fuente : fuentes) {
switch (fuente.tipo) {
case "sharepoint" -> checkSharePoint(fuente);
case "gsheet" -> checkGSheet(fuente);
// mqtt y oracle_cdc son push, no polling
}
}
}
void checkSharePoint(FuenteDatos fuente) {
// Usa el binding de infra "microsoft-graph" (YAML)
// pero la ruta concreta viene de la BBDD (fuente.configuracion)
var config = JsonParser.parse(fuente.configuracion);
var cambios = dapr.invokeBinding("microsoft-graph",
"list", config.get("path"));
if (hayCambios(cambios, fuente.ultimaEjecucion)) {
dapr.publishEvent("pubsub", "fuente.cambio-detectado",
new CambioDetectado(fuente.id, cambios));
}
}
}
Resultado: para añadir una nueva carpeta de SharePoint, un Responsable de Departamento va a la UI de administración, rellena un formulario y pulsa «Guardar». Sin commits, sin YAML, sin reiniciar nada.
5.4 Resumen de Casos de Uso ETL¶
| Caso de Uso | YAML (infra, 1 vez) | BBDD+UI (dinámico) | Código Java |
|---|---|---|---|
| Nuevo Excel en SharePoint | microsoft-graph binding |
Ruta de la carpeta + periodicidad | @Scheduled polling |
| Polling de GSheets | — (API directa) | Sheet ID + rango + periodicidad | @Scheduled polling |
| CDC en Oracle (SIRENO) | postgresql binding |
Tablas a monitorizar | CDC listener |
| IoT sensores MQTT | mqtt3 binding |
Topics por sensor/nave | Push automático |
| Datos Copernicus | — (API directa) | Variables + zona geográfica | @Scheduled diario |
| Alerta por email | smtp output binding |
Destinatarios por departamento | Dapr Pub/Sub event |
| Resumen diario | — | Departamentos + hora | @Scheduled + LangChain4j |
6. MLOps en Detalle — Ciclo de Vida de un Modelo¶
6.1 Pipeline de Entrenamiento¶
flowchart TB
subgraph Datos ["1. Preparación de Datos"]
D1["DVC: versionar dataset"]
D2["Scripts de aumento"]
D3["Split train/val/test"]
end
subgraph Entreno ["2. Entrenamiento"]
E1["Configurar hiperparámetros"]
E2["Fine-tuning LoRA/QLoRA"]
E3["MLflow: log de métricas"]
end
subgraph Validacion ["3. Validación"]
V1["Tests de precisión"]
V2["Tests de latencia"]
V3["Tests de regresión"]
end
subgraph Deploy ["4. Despliegue"]
STAGING["MLflow: Staging"]
PROD["MLflow: Production"]
OLLAMA2["Copiar a Ollama"]
end
D1 --> D2 --> D3 --> E1 --> E2 --> E3
E3 --> V1 --> V2 --> V3
V3 -->|"pasa"| STAGING -->|"aprobación"| PROD --> OLLAMA2
V3 -->|"falla"| E1
style D1 fill:#3498db,color:#fff
style E3 fill:#9b59b6,color:#fff
style PROD fill:#2ecc71,color:#fff
6.2 Registro de Modelos — Estado Completo¶
| Modelo | Versión Actual | Stage | Dataset Entrenamiento | Precisión | VRAM | Departamento |
|---|---|---|---|---|---|---|
| Qwen2.5-VL 7B | v1.0 (base) | Production | — | Generalista | 5 GB | Todos |
| YOLOv11-FathomNet | v2.1 | Production | FathomNet 2025 | mAP 87% | 2 GB | Pesquerías |
| U-Net-Otolitos | v1.3 | Production | ICES SmartDots + IEO | MAE 0,8 años | 1 GB | Pesquerías |
| ResNet-ViT-Patologías | v1.0 | Staging | FAO Aqua-Diseases | 94% | 3 GB | Acuicultura |
| CLIP-IEO | v1.1 | Production | iNaturalist+IEO | Similarity 0,89 | 1 GB | Todos |
| OceanGPT | v1.0 (base) | Production | — | Benchmark OceanBench | 4 GB | Medio Marino |
[!NOTE] Cada modelo tiene su propio ciclo de vida: se entrena, se valida, pasa a Staging, se aprueba por un Responsable, y pasa a Production en Ollama. Si la precisión cae en producción (drift), se re-entrena con datos nuevos.
6.3 Entrenamiento como Dapr Workflow¶
El entrenamiento LoRA (horas/días) es un proceso largo ideal para Dapr Workflow:
// Dapr Workflow — Entrenamiento LoRA
@Workflow(name = "EntrenamientoLoRAWorkflow")
public class EntrenamientoLoRA extends DaprWorkflow {
@Override
public WorkflowStub create() {
return ctx -> {
var config = ctx.getInput(ConfigEntrenamiento.class);
// 1. Preparar dataset (DVC checkout)
ctx.callActivity("prepararDataset", config.datasetVersion());
// 2. Entrenar (puede tardar horas)
var resultado = ctx.callActivity("entrenar", config, ResultadoEntrenamiento.class);
// 3. Evaluar
var metricas = ctx.callActivity("evaluar", resultado, Metricas.class);
// 4. Decisión automática
if (metricas.precision() >= config.umbralMinimo()) {
// Registrar en MLflow como Staging
ctx.callActivity("registrarMLflow", resultado, "Staging");
// Notificar para aprobación humana
var aprobacion = ctx.waitForExternalEvent("aprobacion", Boolean.class);
if (aprobacion) {
ctx.callActivity("promoverProduccion", resultado);
ctx.callActivity("desplegarOllama", resultado);
}
} else {
ctx.callActivity("notificarFallo", metricas);
}
};
}
}
7. Componentes del Stack MLOps¶
flowchart TB
subgraph MLOps ["Plataforma MLOps"]
subgraph Datos2 ["Gestión de Datos"]
DVC3["DVC - Versionado datasets"]
MINIO2["MinIO - Almacenamiento"]
end
subgraph Modelos ["Gestión de Modelos"]
MLFLOW["MLflow - Registro"]
OLLAMA3["Ollama - Serving"]
end
subgraph Orquestacion ["Orquestación"]
DAPR3["Dapr Workflow - Pipelines"]
BIND2["Dapr Bindings - Triggers"]
LC2["LangChain4j - Agentes"]
end
subgraph Observabilidad ["Observabilidad"]
PROM["Prometheus - Métricas"]
GRAF["Grafana - Dashboards"]
OTEL["OpenTelemetry - Tracing"]
end
end
DVC3 --> MLFLOW
MLFLOW --> OLLAMA3
BIND2 --> DAPR3
DAPR3 --> LC2
LC2 --> OLLAMA3
OLLAMA3 --> PROM
PROM --> GRAF
DAPR3 --> OTEL
style MLOps fill:#1a1a2e,color:#fff
style Modelos fill:#9b59b6,color:#fff
style Orquestacion fill:#e74c3c,color:#fff
Docker Compose — Servicios Adicionales¶
| Servicio | Imagen | Puerto | Función | Licencia |
|---|---|---|---|---|
mlflow |
ghcr.io/mlflow/mlflow |
5000 | Registro de modelos, experiment tracking | Apache 2.0 |
prometheus |
prom/prometheus |
9090 | Métricas de modelos y sistema | Apache 2.0 |
grafana |
grafana/grafana-oss |
3000 | Dashboards de monitorización | AGPL-3.0 |
[!TIP] Todo el stack adicional es 100% open-source con licencias Apache/MIT/AGPL. Sin dependencias de licencias propietarias ni telemetría forzada.
8. Viabilidad: ¿Por Qué Quarkus+Dapr y No Pekko?¶
| Capacidad Agéntica | Quarkus + Dapr + LangChain4j | Pekko |
|---|---|---|
| Agente IA con tool calling | @RegisterAiService + @Tool — 15 líneas |
Actor custom — 200+ líneas |
| Orquestación de pipelines | Dapr Workflow — estado persistente automático | Pekko Persistence — manual, complejo |
| Triggers externos | Dapr Bindings — YAML declarativo | Actor custom — código por cada fuente |
| Pub/Sub | Dapr Pub/Sub — desacoplado | Pekko EventStream — acoplado al cluster |
| Multi-modelo | LangChain4j @Named — config CDI |
Routing manual entre actores |
| Procesos largos | Dapr Workflow — sobrevive a reinicios | Pekko Persistence — requiere configuración |
| Reducción de código | ~90% menos vs Pekko | — |
[!IMPORTANT] No hay ningún escenario del IEO donde Pekko sea mejor que Dapr. Los casos de uso donde Pekko brilla (telecoms de alta frecuencia, trading de μs de latencia) no aplican a un centro de investigación oceanográfica. El análisis de viabilidad confirma que 7 de 10 componentes son de migración baja.
9. Roadmap de Implementación¶
gantt
title Despliegue MLOps + Sistema Agéntico
dateFormat YYYY-MM-DD
axisFormat %B %Y
section Fase 1
MLflow registro de modelos :m1, 2026-04-01, 2w
DVC para datasets de otolitos :m2, after m1, 1w
Dapr Bindings SharePoint/GSheets :m3, after m1, 2w
LangChain4j agente Pesquerías :m4, after m3, 3w
section Fase 2
Dapr Workflows ETL por dpto :m5, after m4, 3w
MLflow pipeline entrenamiento :m6, after m4, 2w
Grafana dashboards de modelos :m7, after m6, 2w
section Fase 3
Multi-agente departamental :m8, after m7, 3w
Dapr Agents en K8s :m9, after m8, 2w
Monitorización de drift :m10, after m9, 2w
Documentos Relacionados¶
| Nivel | Documento | Descripción |
|---|---|---|
| Investigación | Análisis LLM Departamental | Los 9+ modelos que esta capa gestiona |
| Investigación | Gobernanza de Datasets | Datasets que alimentan el entrenamiento |
| Investigación | Bancos de Datos Animales | Fuentes intl., APIs, datasets de imágenes, almacenamiento |
| Arquitectura | Viabilidad Pekko→Dapr | Análisis completo Pekko vs Dapr |
| Arquitectura | Arquitectura IA | Pipeline CAG+RAG que los agentes usan |