Curso de construcción de agentes desde cero
Un curso práctico para construir agentes con modelos de lenguaje locales, desde cero y solo con código, para entender qué hay debajo de un “agente” antes de decidir qué framework (o ninguno) usar.
1. El viaje del curso
Este curso construye agentes desde cero, solo con código, ejecutándose en una máquina local con Ollama, sin frameworks obligatorios ni servicios en la nube. La idea es entender el mecanismo antes que las abstracciones.
La primera parada del viaje no es una técnica, sino el concepto que sostiene todo lo demás: el tool calling (o llamada a herramientas). Es la capacidad del modelo de interrumpir su propia respuesta para pedir que se ejecute una función externa (buscar en internet, consultar una base de datos, hacer un cálculo) y seguir razonando con lo que esa función le devuelve. Eso es justo lo que separa un modelo que solo predice texto de uno que puede actuar: en rigor, un agente no es más que un LLM que usa herramientas en un bucle. Por eso el curso arranca ahí. La primera lección, el mecanismo desnudo, no es otra cosa que la forma más simple de implementar el tool calling: una sola herramienta y ningún bucle, para ver el truco a plena luz antes de añadirle nada.
El recorrido:
- El mecanismo desnudo: cómo el modelo “pide” una herramienta y el código la ejecuta.
- Búsqueda web: dar al modelo acceso a internet, primero con un buscador público y luego con uno propio y soberano.
- Un agente de verdad: el bucle que deja al modelo decidir cuántas herramientas usar y cuándo parar.
- Diseño de herramientas: por qué la descripción de una tool decide si el agente acierta.
- Datos reales: conectar el agente a una base de datos sin que el modelo escriba SQL.
- Las tres arquitecturas: el mismo agente a mano, con MCP y con LangGraph.
- La ruta low-code y la gobernanza: herramientas para usuarios no técnicos y cómo se controla todo esto.
Al final no solo se sabe construir un agente: se entiende qué hay debajo de cualquier framework y se puede decidir, con criterio, cuándo usarlo.
Materiales necesarios
Software:
- Ollama, para servir el modelo en local.
- Un modelo con soporte de tool calling. En el curso se usa
qwen2.5:14b-instruct-q4_K_M (generalista; los modelos coder deciden peor
cuándo usar herramientas, se ve en “Lecciones prácticas”).
- Python 3 y, según la lección, las librerías: ollama, requests, ddgs,
beautifulsoup4, mcp, y langgraph + langchain-ollama.
- Docker (opcional), para levantar SearXNG en la lección de búsqueda soberana.
Hardware (la inferencia local depende mucho de la GPU, y aquí Mac y PC difieren):
- Mac (Apple Silicon, M1–M4): la memoria es unificada (la comparten CPU y GPU) y Ollama usa la GPU vía Metal automáticamente. Un 14B en q4 (~9 GB) corre bien con ≥16 GB de RAM; con 24 GB sobra margen. Es la plataforma más cómoda para empezar (un Mac moderno ya trae GPU apta para inferencia).
- Windows/Linux (PC): lo que importa es una GPU NVIDIA dedicada con suficiente VRAM (Ollama la aprovecha por CUDA). Para un 14B en q4 hacen falta ~10–12 GB de VRAM (p. ej. RTX 3060 12 GB, 4070 o superior). Ojo: las GPU integradas (Intel o AMD de la placa base) normalmente no aceleran la inferencia en Ollama; en GPU AMD dedicada el soporte es parcial (ROCm, sobre todo en Linux). Sin una GPU así, la carga recae en la CPU.
- Solo CPU (sin GPU adecuada): funciona, pero va lento. Un 14B puede quedarse en pocos tokens por segundo (respuestas de decenas de segundos). Si solo hay CPU, conviene bajar a un modelo de 7–8B en q4 (~5 GB) y tener paciencia; la RAM disponible debe superar el tamaño del modelo más el del contexto.
Probado en
- Mac mini M4 Pro, 24 GB (Apple Silicon, memoria unificada). El 14B en q4 (~9 GB) corre entero en GPU; los modelos de ~18 GB se desbordan a CPU y van lentos (se ve en “Lecciones prácticas”).
2. ¿Qué es tool calling?
Un LLM, por sí solo, funciona como un oráculo: le preguntas y responde con lo que aprendió durante su entrenamiento, nada más. No conoce lo posterior a su fecha de corte ni nada privado: la última versión de una librería, el tiempo de hoy, los expedientes de una base de datos. O lo sabe de memoria, o lo inventa.
El tool calling (llamada a herramientas) es la técnica que convierte ese oráculo en un agente: en vez de responder solo con lo que lleva dentro, el modelo puede pedir usar herramientas que se ponen a su disposición (buscar en internet, consultar una base de datos, llamar a una API). El modelo decide qué necesita; el código lo ejecuta y le devuelve el resultado. Sobre esta idea simple se construye todo lo demás.
Idea central: el modelo NO ejecuta nada. Decide. El código ejecuta. Un agente fiable es siempre prompt (guía) + código (red de seguridad).
Por qué tool calling: del oráculo al agente
Código de esta sección: 01_minimo.py, 02_busqueda_web.py
La diferencia es de naturaleza, no de grado:
- Oráculo: el LLM responde con su conocimiento interno, congelado en el entrenamiento. Si no lo sabe, alucina o se disculpa.
- Agente: ante una carencia, el LLM actúa, llama a una herramienta, lee el resultado y responde con datos frescos o reales (de internet, de la BD…).
Pasar del oráculo al agente es justo lo que habilita el tool calling, y es el hilo conductor de todo el curso. Las tres piezas que lo hacen posible se ven a continuación: los dos prompts, el esquema de herramientas y la secuencia de ejecución (el baile).
Los dos prompts
Código de esta sección: 01_minimo.py, 04_agente.py
El modelo recibe en cada llamada dos prompts con roles distintos, más el catálogo de herramientas:
-
user: la pregunta. En este caso, trivial:¿Qué versión estable de Python es la más reciente y cuándo salió?
-
system: el “procedimiento” o estrategia. Aquí vive casi toda la inteligencia del comportamiento. El del curso (de04_agente.py):Eres un asistente riguroso con acceso a internet mediante herramientas. 1. Si la pregunta trata de hechos recientes, versiones, fechas o cifras, usa buscar_web. 2. No te fíes solo de los títulos/resúmenes: elige la fuente más fiable (prioriza sitios oficiales) y usa leer_pagina para leer su contenido real. 3. Si aparecen varios números de versión, la MÁS RECIENTE es la de número más alto (3.14 > 3.12). 4. Responde de forma concreta y cita las URLs usadas. Nunca inventes fechas ni datos: si no los confirmaste leyendo, dilo.
El system prompt se construye corrigiendo los fallos típicos del modelo. Cada línea responde a un error que un LLM tiende a cometer:
| Línea del system | Fallo del modelo que previene |
|---|---|
| “usa buscar_web” | responder de memoria, con información desactualizada |
| “lee la fuente real con leer_pagina” | fiarse de títulos y resúmenes sueltos, a menudo ambiguos |
| “número más alto = más reciente” | no saber ordenar versiones (creer que la 3.12 es más nueva que la 3.14) |
| “nunca inventes fechas” | inventar (alucinar) datos o fechas que no ha confirmado |
Matiz crucial: el system prompt NO es un algoritmo que se ejecuta
Es una sugerencia/persuasión, no código determinista.
| Algoritmo de verdad (código) | System prompt |
|---|---|
| Se ejecuta siempre igual | El modelo lo sigue probabilísticamente |
if/else deterministas |
“Tiende a” hacer caso |
| Garantizado | Puede ignorarlo o equivocarse |
El control determinista de verdad está en el código (el bucle responder):
qué función se ejecuta, qué se devuelve, cuántos turnos máximo. Cuanto más
pequeño y local el modelo, más peso tiene que llevar el código.
El esquema TOOLS
Código de esta sección: 01_minimo.py, 04_agente.py
TOOLS es la lista de herramientas que se le entrega al modelo en cada llamada.
Es su único “catálogo”: lo único que el modelo llega a conocer de las herramientas
disponibles. Ahí se le dice qué herramientas existen, cuándo usarlas y qué
argumentos aceptan.
Cada herramienta se describe con la misma estructura. A continuación se desglosa
pieza a pieza, con la herramienta leer_pagina como ejemplo:
{
"type": "function", # (1) envoltorio fijo
"function": {
"name": "leer_pagina", # (2) identificador
"description": ( # (3) PARA EL MODELO: cuándo usarla
"Descarga y devuelve el texto real de una página web dada su URL. "
"Úsala tras buscar, para leer la mejor fuente y confirmar datos "
"concretos (fechas, versiones, cifras) antes de responder."
),
"parameters": { # (4) JSON Schema estándar
"type": "object", # (4a) los args son un objeto
"properties": { # (4b) un campo por argumento
"url": { # (4c)
"type": "string",
"description": "URL de la página a leer."
}
},
"required": ["url"], # (4d) args obligatorios
},
},
}
namedebe coincidir EXACTAMENTE con la clave enFUNCIONES = {"leer_pagina": leer_pagina}.descriptiones lenguaje natural para el modelo. Es lo único que ve de la herramienta (no ve el código Python). Conviene indicar no solo qué hace, sino cuándo usarla.parameterses JSON Schema estándar. Conviene usarenumpara acotar valores (oro para modelos pequeños: les quita margen de error).
El esquema es un contrato bilingüe:
| Parte | ¿Para quién? |
|---|---|
name, estructura, type, required |
el código (despachar y validar) |
las description |
el modelo (decidir y rellenar) |
La secuencia completa (el “baile”)
Código de esta sección: 04_agente.py
Esto es lo que sucede, paso a paso, al ejecutar 04_agente.py:
- El modelo recibe
system+user+TOOLS. Según el system, ante una pregunta sobre un dato actual, debe buscar. - Emite una llamada, no una respuesta:
buscar_web(query="..."). Se detiene y espera (el modelo no tiene acceso a internet). - El código detecta la llamada, busca la función en
FUNCIONES, la ejecuta (consulta a SearXNG) y obtiene una lista de resultados. - El código añade el resultado como mensaje
role: "tool"y vuelve a llamar al modelo con toda la conversación. - El modelo lee los resultados y, siguiendo el system (“lee la fuente”), emite
otra llamada:
leer_pagina(url=...)con la fuente más fiable. - El código ejecuta y devuelve el texto real de la página.
- Si necesita confirmar algún dato, el modelo puede emitir más llamadas (otra búsqueda, otra página…), repitiendo el ciclo.
- El código ejecuta cada una y le devuelve el contenido.
- Cuando ya tiene lo que necesita, el modelo responde con texto normal (sin llamadas), citando las fuentes usadas.
- El código ve que no hay llamadas pendientes → es la respuesta final → la imprime y sale del bucle.
Diagrama
flowchart TD
A["system + user + TOOLS"] --> M{"el modelo:
¿pide herramienta?"}
M -- "no" --> F["respuesta final"]
M -- "sí" --> C["el código ejecuta la herramienta"]
C -- "resultado (role: tool)" --> M
Se alternan dos protagonistas
| Turno | Quién manda | Qué hace |
|---|---|---|
| impares | el modelo | decide: ¿pido herramienta o respondo? |
| pares | el código | ejecuta de verdad y devuelve el resultado |
El modelo nunca toca el mundo real; el código nunca decide qué hacer.
max_turnos es el seguro por si el modelo pidiera herramientas sin parar.
A partir de aquí, cada sección con un ejemplo se cierra mirándolo por sus dos pivotes, los que de verdad deciden si un agente es utilizable en serio:
- Gobernanza: quién controla qué puede hacer el agente y con qué límites.
- Explicabilidad: poder reconstruir por qué hizo lo que hizo (qué herramientas usó, con qué argumentos y con qué resultado).
Explicabilidad: cada paso queda en la lista de mensajes (qué herramienta pidió, con qué argumentos y qué devolvió), así que la propia conversación es la traza de lo que hizo.
3. Lecciones prácticas (Mac M4 Pro, 24 GB)
Código de esta sección: 04_agente.py
- Coder ≠ instruct para agentes:
qwen2.5-coderescribe la llamada como texto en vez de usar el canaltool_calls. Para agentes, usa un instruct generalista:qwen2.5:14b-instruct-q4_K_Mfuncionó bien. - Que quepa en GPU: Apple Silicon da a la GPU ~70% de la RAM (24 GB → ~16 GB). Modelos de 18 GB (qwen3-coder:30b, gemma4:26b) se desbordan a CPU y van lentos. Conviene un 14B q4 (~9 GB).
- Llamadas fugadas: algunos modelos emiten la llamada como texto. Un agente
robusto parsea ambos canales (ver
llamadas_fugadas_en_textoen04_agente.py). temperature: 0reduce aleatoriedad en la decisión y evita tokens basura.- Mejorar un RAG no es solo cambiar de modelo: prompt afinado + leer la fuente real + parseo robusto fue lo que marcó la diferencia.
4. ¿Qué es un “agente”?
Un agente es un LLM que usa herramientas en un bucle, decidiendo él mismo qué hacer a continuación hasta cumplir el objetivo.
Con LLM + herramientas + un orquestador que ejecuta en bucle lo que el
modelo pide, ya hay un agente. 04_agente.py lo es. Los tres ingredientes
mínimos:
| Ingrediente | En el código del curso |
|---|---|
| LLM que decide | qwen2.5:14b eligiendo qué herramienta pedir |
| herramientas | buscar_web, leer_pagina |
| orquestador que ejecuta en bucle | la función responder() |
La clave: el bucle con decisión del modelo
Lo que convierte esto en agente (y no en otra cosa) es que el modelo controla el flujo:
- Workflow (NO es agente): los pasos se fijan en código (“busca → lee la primera URL → resume”). El LLM rellena huecos, pero la ruta la fija el código. Determinista.
- Agente (SÍ): el modelo decide en cada vuelta cuántas búsquedas hacer, qué páginas leer y cuándo parar. La ruta NO está escrita de antemano. (En la ejecución el modelo eligió 1 búsqueda + 2 lecturas; nunca se programó.)
Si se quita el bucle y queda una sola llamada con una herramienta, ya no es un agente: es solo function calling.
“Ser agente” es un espectro, no un sí/no
El del curso es un agente mínimo y honesto, pero básico. Lo que separa un agente de juguete de uno serio (y que aún falta):
- Memoria más allá de la conversación actual (entre sesiones).
- Planificación explícita (descomponer un objetivo grande en subtareas).
- Manejo de errores y reintentos (si una herramienta falla, ¿qué hace?).
Aquí solo hay
max_turnoscomo red de seguridad. - Más herramientas y criterio para elegir entre muchas.
- Reflexión / autocrítica antes de dar la respuesta final.
Conclusión
Un agente no es nada mágico: es un LLM + herramientas + un bucle que cede al
modelo el control de flujo. Todo lo demás (memoria, planificación, multi-agente,
frameworks como LangGraph) son capas de sofisticación sobre ese mismo esqueleto
que se construyó a mano en responder().
5. Experimento: ¿elige bien con 4 herramientas?
Código de esta sección: 05_multiherramienta.py
Hasta aquí el agente tenía una o dos herramientas, así que elegir era trivial. Pero ¿qué pasa cuando hay varias, cada una buena para un tipo de pregunta distinto? ¿Sabe el modelo escoger la adecuada en cada caso, sin que nadie se lo indique? Para comprobarlo, se le dan cuatro herramientas muy diferentes y se le hacen preguntas de naturaleza distinta.
Herramientas: buscar_web, leer_pagina, calcular (aritmética local),
fecha_y_hora (sin argumentos).
Resultado: acierta la herramienta en los 4 casos
| Pregunta | Herramienta(s) elegida(s) | Correcto |
|---|---|---|
1234 * 5678 + 99 |
calcular |
✓ |
| ¿Qué día es hoy? | fecha_y_hora |
✓ |
| Última versión de Python | buscar_web → leer_pagina |
✓ |
| Años desde un año dado | fecha_y_hora → calcular |
✓ |
Lo que demuestra
- Enrutado por intención: distingue “aritmética” de “fecha” de “está en
internet” sin que nadie se lo diga explícitamente. Esa discriminación sale de
las
descriptionde cada herramienta + el system prompt. - Encadena herramientas distintas (pregunta 4): deduce que primero
necesita el año actual (
fecha_y_hora) y luego restar (calcular). Nadie programa esa secuencia → comportamiento de agente puro. - Busca + verifica (pregunta 3): mantiene el patrón de leer la fuente oficial antes de afirmar.
Los límites de usar un modelo 14B
- Llamada redundante: a veces repite una llamada (p. ej.
fecha_y_horados veces seguidas). Inofensivo, pero ineficiente. - Artefacto de formato: de vez en cuando se cuela algún carácter de más (p. ej. una barra invertida en “hace \35 años”). Glitch típico de un 14B cuantizado.
Ninguna afecta al resultado, pero marcan el límite: el enrutado de herramientas es sorprendentemente bueno; el pulido del texto y la eficiencia, no tanto.
Conclusión
El mecanismo escala a más herramientas. La clave para que siga acertando es que
las description sean distinguibles entre sí: si dos herramientas se solapan,
el modelo empieza a dudar. Diseñar herramientas con responsabilidades claras y
descripciones que no se pisen es tan importante como el propio modelo.
Explicabilidad: la traza muestra qué herramienta eligió para cada pregunta; su criterio de decisión queda a la vista y se puede revisar.
6. La estrategia: system prompt + tool descriptions (reparto de trabajo)
Lo que hizo que el agente de 4 herramientas enrutara bien no fue suerte: fue un
reparto deliberado entre el system prompt (política global) y cada
description (disparador local). Esta es la estrategia seguida.
Principio de reparto
| Va en el SYSTEM PROMPT | Va en la DESCRIPTION de cada tool |
|---|---|
| Política y actitud global (“sé riguroso”) | Qué hace ESA herramienta concreta |
| Coordinación entre herramientas (“encadena varias si hace falta”) | Cuándo se dispara ESA herramienta |
| Reglas transversales (“no calcules de memoria”) | El formato del argumento (ej. '1234 * 5678') |
Regla mnemotécnica: lo que afecta a UNA herramienta → su description; lo que
afecta a VARIAS o al comportamiento general → el system.
El system prompt utilizado (y por qué cada frase)
Eres un asistente riguroso con varias herramientas. Elige la adecuada a cada
pregunta. No calcules ni adivines fechas de memoria: usa calcular y fecha_y_hora.
Para hechos de internet usa buscar_web y confirma en la fuente con leer_pagina.
Si una tarea requiere varios pasos, encadena varias herramientas.
Responde de forma concreta.
| Frase | Para qué |
|---|---|
| “Elige la adecuada a cada pregunta” | activa el modo de enrutado |
| “No calcules ni adivines de memoria” | anti-alucinación: le quita el instinto de improvisar |
| “buscar_web y confirma con leer_pagina” | impone la secuencia entre dos herramientas |
| “encadena varias herramientas” | autoriza explícitamente el multi-paso |
Las descriptions (patrón de redacción)
Cada description sigue la misma plantilla: qué hace + cuándo usarla, y se
escribe para NO solaparse con las demás.
| Herramienta | “Úsala cuando…” (el disparador) |
|---|---|
buscar_web |
“hechos recientes, noticias, versiones, datos que no sepas” |
leer_pagina |
“tras buscar, para confirmar datos en la fuente” |
calcular |
“SIEMPRE para cálculos numéricos, en lugar de que lo calcule el propio modelo” |
fecha_y_hora |
“cuando necesites ‘hoy’, ‘ahora’, el día de la semana o cálculos con fechas” |
Tres técnicas concretas aplicadas:
- Cada description incluye un “Úsala cuando…”: no basta con decir qué hace; hay que decir el caso de uso. Es lo que el modelo compara contra la pregunta.
- Palabras-disparador que no se pisan: “cálculo numérico” vs “fecha/hora” vs “internet” vs “tras buscar”. Sin solape no hay duda.
SIEMPRE/ mayúsculas para vencer un instinto del modelo: encalcularse fuerza que no haga la cuenta de cabeza (cosa que un LLM hace mal). Reservar el énfasis para los pocos puntos donde el modelo tiende a desobedecer.
Redundancia deliberada system ↔ description
Nótese que la regla “no calcules de memoria” aparece dos veces: en el
system (“no calcules de memoria”) y en la description de calcular (“SIEMPRE…”).
Es intencionado: reforzar en ambos canales una regla crítica sube mucho la tasa
de acierto en modelos pequeños. No es un error de duplicación.
7. Contraexperimento: descripciones SOLAPADAS
Código de esta sección: 06_solapadas.py
Para entender el límite, ¡hagamos el experimento contrario al de la sección 6! Metamos a propósito tres herramientas de búsqueda casi sinónimas, con descripciones que se pisan y las tres haciendo lo mismo (consultar SearXNG), y veamos si el modelo elige de forma consistente.
Descripciones (solapadas a propósito):
- buscar_web: “Busca información actualizada en internet.”
- buscar_datos: “Busca datos y hechos en internet. Úsala para versiones, fechas y cifras.”
- buscar_general: “Búsqueda general en internet para responder cualquier pregunta.”
Resultado: el modelo se confunde
| Pregunta | Herramienta(s) elegida(s) | Observación |
|---|---|---|
| Versión de Python | buscar_datos |
ok |
| Versión de Node.js (misma naturaleza) | buscar_datos + buscar_web |
incoherente |
| Mundial de fútbol | buscar_web |
razonable |
| Habitantes de Madrid | buscar_datos ×2 |
duplicada |
Los tres síntomas del solape
- Incoherencia entre preguntas equivalentes: “última versión de Python” y
“última versión de Node.js” son la misma pregunta con distinto sujeto, pero
enrutaron distinto. Sin un ganador claro, el modelo titubea, incluso con
temperature: 0. - Llamadas redundantes: llamó a dos herramientas donde bastaba una (“por si acaso”), gastando tiempo y tokens.
- Herramienta muerta:
buscar_generalno se eligió ni una vez. Su descripción era la más vaga, sin palabra-disparador. Una herramienta mal descrita es invisible.
Contraste 05 vs 06 (la prueba A/B)
| Exp. 05 (distinguibles) | Exp. 06 (solapadas) | |
|---|---|---|
| Coherencia | misma pregunta → misma herramienta | misma pregunta → rutas distintas |
| Eficiencia | una llamada por intención | llamadas duplicadas |
| Cobertura | todas las herramientas útiles | una nunca se usó |
La lección
El cuello de botella de un agente con muchas herramientas no es el modelo, es
el diseño del catálogo. Cada herramienta necesita un disparador propio y
exclusivo en su description. Si dos se solapan, no añaden capacidad: añaden
duda. Mejor una herramienta bien descrita que tres sinónimas.
Explicabilidad: cuando dos herramientas compiten, cuesta justificar por qué eligió una u otra; el solape degrada la capacidad de explicar la decisión.
8. Herramienta sobre una base de datos
Código de esta sección: 07_expedientes.py
Hasta aquí las herramientas traían información de internet. Pero el mismo mecanismo sirve para algo más potente: dar al agente acceso a datos propios y estructurados, los que viven en una base de datos. Es el salto de “juguete que busca en la web” a “asistente sobre datos reales”.
Y, como siempre, solo cambia el cuerpo de la herramienta: el modelo, el bucle
y el esquema TOOLS son idénticos; donde antes había una llamada a un buscador,
ahora hay una consulta a la base de datos. La demo usa SQLite (expedientes.db,
creado con crear_expedientes_db.py), pero el diseño está pensado para apuntar a
un Oracle/Postgres real con un cambio mínimo.
Dos herramientas (distinguibles, según la sección 6):
- buscar_expedientes(estado, tipo, anio, responsable, limite): lista con filtros.
- detalle_expediente(codigo): todos los datos de uno concreto.
Resultado: 4/4 consultas correctas
| Pregunta | Acción del modelo | OK |
|---|---|---|
| Incidentes abiertos | buscar_expedientes(tipo=incidente, estado=abierto) |
✓ |
| Abiertos en 2026 | buscar_expedientes(estado=abierto, anio=2026) |
✓ |
| ¿Cuántos lleva Marta Ruiz? | buscar_expedientes(responsable='Marta Ruiz') → contó 3 |
✓ |
| Detalle EXP-2025-008 | detalle_expediente(codigo=...) |
✓ |
Lo destacable
- Lenguaje natural → filtros estructurados: “incidentes abiertos” se
convirtió en
tipo='incidente', estado='abierto'. Losenumdel esquema le dieron al modelo el vocabulario exacto de los datos. - Razona sobre los resultados: en “¿cuántos lleva Marta Ruiz?” no solo buscó: contó (3) y agregó los tipos. El SQL devuelve filas; la síntesis la hace el modelo.
- Enruta bien entre las dos herramientas: para un código concreto usó
detalle_expediente, nobuscar_expedientes.
Dos blindajes clave para producción
- El modelo NO escribe SQL. Se exponen consultas parametrizadas y acotadas
(los filtros usan
enum). El modelo elige el qué (los valores); el cómo (la sentencia) lo controla el código. Evita inyección SQL y que el modelo toque nada indebido. Regla de oro otra vez: el prompt sugiere, el código garantiza. - Cambiar a la BD real es una función. En
07_expedientes.py, la función_conectar()lleva la nota: se sustituye SQLite pororacledb.connect(...)(opsycopg2para Postgres) y se ajustan nombres de tabla/columnas. Herramientas, bucle y prompts quedan igual.
Una capa así (solo lectura, consultas acotadas) es justo la fachada segura que encaja en una modernización strangler sobre Oracle.
Explicabilidad: cada respuesta es trazable hasta la consulta exacta y las filas devueltas; se reconstruye de dónde salió cada dato.
9. La misma herramienta, ahora por MCP
Código de esta sección: mcp_expedientes_server.py, 08_cliente_mcp.py
Hasta aquí, cada agente lleva sus herramientas dentro: el TOOLS y las
FUNCIONES están escritos en el propio script. Funciona, pero esas herramientas
no se pueden reutilizar desde otro agente ni compartir con otros equipos. La
siguiente pregunta natural es: ¿y si las herramientas vivieran aparte, como un
servicio independiente que cualquier agente pueda descubrir y usar? Esa es,
exactamente, la idea de MCP.
MCP (Model Context Protocol) estandariza lo que se hizo a mano: cómo un agente
descubre y llama herramientas. Se refactorizó el tool de expedientes a MCP
conservando 07 intacto como versión no-MCP, para compararlas lado a lado.
Qué cambia (y qué NO)
Conviene recordar la arquitectura de 07: el TOOLS y FUNCIONES viven DENTRO del agente.
MCP rompe ese acoplamiento en dos procesos que hablan por un protocolo:
flowchart LR
subgraph Cliente["Cliente / Host (08_cliente_mcp.py)"]
LLM["LLM + bucle"]
end
subgraph Servidor["Servidor MCP (mcp_expedientes_server.py)"]
T["buscar_expedientes
detalle_expediente"]
end
LLM <-->|"protocolo MCP (stdio)"| T
07_expedientes.py (no-MCP) |
08 + servidor MCP |
|
|---|---|---|
De dónde sale TOOLS |
escrito a mano | descubierto con list_tools() |
| Quién escribe el esquema JSON | a mano | FastMCP lo genera del type hint + docstring |
| Cómo se ejecuta la tool | FUNCIONES[nombre](**args) |
session.call_tool(...) por el protocolo |
| Dónde vive la lógica | dentro del agente | proceso aparte, reutilizable |
| El LLM y el bucle de decisión | — | idénticos |
Moraleja: MCP no toca el mecanismo del agente. Solo cambia de dónde vienen las herramientas y cómo se invocan.
El servidor (FastMCP)
No se escribe ningún esquema JSON. El decorador @mcp.tool() lo genera solo:
- los type hints se convierten en
parameters(incluidoLiteral[...]→enum, justo lo que en 07 se escribía a mano); - el docstring se convierte en la
description.
@mcp.tool()
def buscar_expedientes(estado: Literal["abierto","en_tramite","cerrado","archivado"] | None = None, ...) -> str:
"""Busca expedientes... Úsala para 'expedientes abiertos de 2025'..."""
El cliente (los dos cambios de 07 → 08)
- Descubrimiento en vez de catálogo escrito:
python lista = await session.list_tools() ollama_tools = [{"type":"function","function":{ "name": t.name, "description": t.description, "parameters": t.inputSchema}} for t in lista.tools]inputSchemaya es JSON Schema estándar → encaja directo en el formato de Ollama. - Ejecución delegada en vez de diccionario local:
python resultado = await session.call_tool(nombre, dict(args)) texto = "\n".join(c.text for c in resultado.content if hasattr(c, "text"))
El cliente lanza el servidor como subproceso (StdioServerParameters) y habla con
él por stdin/stdout. Es asíncrono porque el cliente MCP lo es.
Resultado
Idéntico a 07 (mismas respuestas en las 3 preguntas). En la salida aparecen
Processing request of type ListToolsRequest / CallToolRequest: es el servidor
registrando los mensajes del protocolo, prueba de que la comunicación va por MCP.
Por qué importa (y el matiz de soberanía)
- Desacoplamiento y reutilización: el servidor de expedientes lo puede usar cualquier agente, no solo este script. Es el formato del “catálogo descargable”.
- Ollama no habla MCP de forma nativa: el cliente hace de puente (descubre por MCP, llama a Ollama con el esquema adaptado). Funciona 100% on-premise.
- Cuándo merece la pena: MCP añade un proceso y un protocolo. Para una sola app es overhead; brilla cuando se quiere compartir herramientas entre varios agentes/equipos. MCP es un protocolo fino (poco peso), a diferencia de un framework como LangChain (ver futura sección sobre LangGraph).
Explicabilidad: cada
tools/list y tools/call
pasa por el protocolo y queda en un punto único, fácil de registrar y auditar.
10. El bucle como grafo: LangGraph
Código de esta sección: 09_langgraph.py
Con MCP se sacaron las herramientas fuera del agente. Queda una última pieza por
mirar: el bucle en sí. Hasta ahora se ha escrito a mano (el while de
responder()), y funciona, pero a medida que un agente crece (memoria, varios pasos, reintentos, varios agentes coordinados) ese bucle artesanal se queda corto.
Para eso existen los frameworks de agentes, y el más extendido hoy es
LangGraph. La pregunta que cierra el curso es: ¿qué aporta un framework frente
a lo que ya se ha construido a mano?
La respuesta tranquilizadora: LangGraph no inventa nada nuevo. Formaliza
exactamente el mismo bucle que se construyó a mano en responder(), pero
expresándolo como un grafo de estados. Es la tercera forma de montar el MISMO
agente de expedientes, y el mapeo con el código a mano es 1:1.
Equivalencias con el código a mano
| El código a mano | LangGraph |
|---|---|
escribir el dict TOOLS |
@tool (genera el esquema del docstring + type hints) |
ollama.chat(tools=...) |
ChatOllama(...).bind_tools(tools) |
FUNCIONES[nombre](**args) |
ToolNode(tools) |
el while de responder() |
StateGraph con nodos y aristas |
| “¿pidió herramienta?” | tools_condition (arista condicional) |
la lista messages |
el estado (MessagesState) |
max_turnos |
los ciclos del grafo (con checkpoints, etc.) |
El grafo
Dos nodos y una arista condicional reproducen el bucle:
grafo = StateGraph(MessagesState)
grafo.add_node("agente", nodo_agente) # llama al LLM
grafo.add_node("tools", ToolNode(TOOLS)) # ejecuta la herramienta
grafo.add_edge(START, "agente")
grafo.add_conditional_edges("agente", tools_condition) # ¿tool? -> tools, si no -> END
grafo.add_edge("tools", "agente") # tras ejecutar, vuelve al modelo
app = grafo.compile()
Ese grafo, dibujado, es el mismo baile de siempre:
flowchart TD
INI([START]) --> A["nodo agente
(llama al LLM)"]
A --> COND{tools_condition}
COND -->|"sí (pide herramienta)"| T["ToolNode
(ejecuta la herramienta)"]
COND -->|"no"| FIN([END])
T --> A
create_react_agent(llm, TOOLS, prompt=SYSTEM) hace todo ese grafo en una línea
(queda comentado al final del archivo).
Detalle revelador: el boilerplate que el framework te ahorra
Para entender el valor real de un framework, conviene fijarse en algo que hubo que resolver a mano y que aquí ha desaparecido.
El problema, recordémoslo. Cuando el modelo decide usar una herramienta,
debería devolver esa petición en un campo estructurado aparte (tool_calls),
separado del texto de su respuesta. Pero los modelos no siempre se portan bien:
qwen2.5, a veces, en lugar de rellenar ese campo, escribe la llamada como texto
normal, mezclada en su respuesta (algo como <tool_call>{"name": ...}</tool_call>).
Si el código solo mira el campo tool_calls, esa llamada “se le escapa” y el
agente se queda colgado. A eso se le llama en este curso una “llamada fugada”.
Lo que hubo que hacer a mano (lecciones 04–08). Se escribió una función,
llamadas_fugadas_en_texto(), que rebusca en el texto de la respuesta por si hay
una llamada escondida ahí, la extrae y la trata como si hubiera venido por el
canal correcto. Es código de fontanería: no aporta nada al problema de negocio,
solo tapa una rareza del modelo. Se arrastró en cada lección con agente.
Lo que pasó con LangGraph. Ese rebusqueo no hizo falta: la pieza
langchain-ollama ya detecta y normaliza por dentro esas llamadas fugadas,
entrega siempre los tool_calls limpios, y el grafo ni se entera del
problema. La función a mano queda eliminada.
La lección. Esto es, en miniatura, exactamente lo que compras con un framework: te ahorra el boilerplate (el código repetitivo de fontanería que rodea al problema real). Y es también, en miniatura, lo que se paga: se confía en que la librería lo siga haciendo bien, se deja de ver (y de controlar) ese detalle, y se asume la dependencia. Ese intercambio (menos código propio a cambio de más dependencia y menos transparencia) es la decisión de fondo de toda la sección 11.
Resultado
Idéntico a 07 y 08 (mismas respuestas, misma BD). Lo único que cambió fue cómo se expresa el bucle.
Explicabilidad: el estado del grafo y los checkpoints permiten inspeccionar y reproducir una ejecución; el precio es confiar en (y entender) la caja del framework.
11. Comparativa: solo código vs MCP vs LangGraph
Las tres construyen el MISMO agente y dan el MISMO resultado. Eligen distinto qué automatizan y cuánto peso añaden.
Tabla resumen
Solo código (07) |
MCP (08) |
LangGraph (09) |
|
|---|---|---|---|
| Esquema de tools | a mano | FastMCP lo genera | @tool lo genera |
| Ejecución | FUNCIONES[n](**a) |
call_tool (protocolo) |
ToolNode |
| El bucle | while propio |
while propio |
grafo de estado |
| Parseo de llamadas | a mano | a mano | lo hace el framework |
| Dependencias | cero | mcp (fina) |
langgraph+langchain (pesadas) |
| Fontanería a escribir | mucha | media | poca |
| Dónde viven las tools | en el agente | proceso aparte reutilizable | en el agente |
Solo código
Pros: cero dependencias; control total; transparencia absoluta (se ve cada byte); ideal para aprender y para entornos con restricciones de soberanía estrictas; sin riesgo de cambios de API de terceros. Contras: hay que mantener todo el boilerplate (parseo, reintentos, límites); no hay persistencia/streaming/human-in-the-loop de regalo; reinventas lo que ya está resuelto; las tools quedan acopladas a la app. Cuándo: aprender; agentes pequeños; máxima soberanía/auditabilidad; cuando “menos magia” es un requisito.
MCP
Pros: desacopla las tools del agente → reutilizables por varios agentes/ equipos; descubrimiento dinámico; ecosistema/catálogo de servidores ya hechos; protocolo fino y abierto (poco peso); el agente y su bucle siguen siendo tuyos. Contras: añade un proceso y un protocolo (más partes móviles); Ollama no habla MCP de forma nativa (hace falta el puente del cliente); overhead injustificado para una sola app sencilla. Cuándo: se quiere compartir herramientas entre varios agentes/aplicaciones; montar un catálogo interno (p. ej. un servidor de expedientes para toda la org); integrarte con hosts que ya hablan MCP.
LangGraph
Pros: elimina casi todo el boilerplate (esquemas, parseo, bucle); te da gratis persistencia, checkpoints, streaming, human-in-the-loop, ciclos y multi-agente; patrón estándar y muy documentado; escala a agentes complejos. Contras: dependencias pesadas y con muchas abstracciones (lo contrario del “mero código”); curva de aprendizaje del framework; quedas atado a sus decisiones y a sus cambios de versión; menos transparencia (“¿qué hace por debajo?”). Cuándo: agentes complejos en producción; hace falta memoria/persistencia/ streaming ya resueltos; el equipo ya usa el ecosistema LangChain.
No son excluyentes
Se combinan: un agente LangGraph puede consumir tools de un servidor MCP
(langchain-mcp-adapters). Y siempre se puede empezar a mano para entender,
y migrar a MCP/LangGraph solo donde el trade-off lo justifique.
Recomendación para este proyecto (soberanía, on-premise)
- Aprender y prototipar: solo código (lo hecho en el curso). Transparente y sin ataduras.
- Compartir la capa de expedientes entre varios agentes/equipos: MCP (fino, abierto, on-premise, encaja con la modernización strangler).
- Framework pesado (LangGraph): solo si el agente crece hasta necesitar persistencia/multi-agente y el ahorro de boilerplate compensa la dependencia. Hasta entonces, el bucle a mano basta y es más auditable.
12. El agente autónomo
Este tema el curso lo abre, no lo cierra: los agentes construidos aquí son reactivos (responden a una pregunta y paran). La autonomía es el siguiente nivel, y conviene entenderlo justo antes de hablar de gobernanza.
¿Qué es la autonomía de un agente?
Todos los agentes de este curso son reactivos: una persona pregunta, el agente decide qué herramientas usar, responde y se detiene. La autonomía es el grado en que un agente decide y actúa por su cuenta, sin que alguien apruebe cada paso. No es un sí/no: se mide en varios ejes:
- Iniciativa: ¿arranca porque alguien pregunta (reactivo) o por sí mismo, por un horario o un evento (proactivo)?
- Longitud de la cadena: ¿da un paso y para, o encadena muchas acciones sin intervención humana?
- Capacidad de actuar: ¿solo lee (buscar, consultar) o también escribe y ejecuta acciones con consecuencias (enviar, modificar, borrar)?
- Nivel de objetivo: ¿se le da una tarea concreta o una meta (“mantén al día los expedientes”) que él mismo descompone en pasos?
- Criterio de parada: ¿decide por sí mismo cuándo ha terminado?
Autonomía hacia dentro: reflexión y autocrítica
Los ejes anteriores describen la autonomía hacia fuera: qué hace el agente en el mundo. Hay otra autonomía, hacia dentro: la capacidad del modelo de juzgar su propio resultado y decidir si lo da por bueno o itera un poco más antes de responder.
El bucle de este curso ya tiene una forma mínima de esto: el agente sigue pidiendo herramientas hasta que él considera que tiene lo suficiente (cuando deja de emitir llamadas). La versión rica (el patrón de reflexión o autocrítica) va más allá: antes de dar la respuesta por definitiva, el modelo evalúa su propio borrador (“¿está completo?, ¿lo he confirmado en una fuente fiable?, ¿cuadra el cálculo?”) y, si no le convence, vuelve a actuar (otra búsqueda, releer, recalcular). Se vio un asomo en la sección 2, cuando el agente lee una segunda página para confirmar un dato en lugar de fiarse del primer resultado.
¿Cómo se consigue? Con código, no con un modelo distinto: es el mismo LLM,
pero el orquestador añade un paso que le pide criticar su propio borrador y, según
esa autoevaluación, decide si vuelve a entrar en el bucle (a veces repartido en dos
papeles: uno que produce y otro que critica). Es ingeniería del bucle (se amplía el responder()), no un cambio de modelo. Aparte están los modelos de razonamiento,
que deliberan internamente antes de responder: parecido en espíritu, pero eso ocurre
dentro del modelo, no en el bucle del agente.
¿Y dónde conviene que ocurra esa reflexión: en el código o dentro del modelo? No
es una cuestión menor, porque toca de lleno los dos pivotes. La reflexión en
código es transparente y gobernable: cada autocrítica e iteración es un paso
visible que se puede registrar, limitar (max_turnos) y auditar; el control queda en
el código. La reflexión dentro del modelo tiende a ser una caja negra: la
deliberación va oculta o resumida, no se controla ni se reproduce, y depende del
proveedor (además suele exigir modelos grandes o de pago, en tensión con la
soberanía). La regla práctica: cuanta más reflexión se delega al interior del modelo,
mejor puede ser la respuesta, pero menos se puede explicar y gobernar; cuanta más
se mantiene en código, más boilerplate, pero más trazabilidad y control. En
contextos de alto riesgo conviene inclinarse por la reflexión en código
(auditable) y usar la interna del modelo como apoyo, no como sustituto del registro.
Es un arma de doble filo: mejora la calidad, pero alarga la cadena y la latencia
y hace menos predecible cuándo se detendrá. Por eso conviene acotarla con un máximo
de iteraciones (como el max_turnos del bucle).
En cuanto a los dos pivotes: la reflexión suele ser interna y de solo lectura, así que apenas toca la gobernanza; pero sí afecta a la explicabilidad, en los dos sentidos: si la autocrítica se registra (qué dudó, por qué reintentó), explica mejor su respuesta; si no, añade pasos opacos difíciles de reconstruir.
¿Total o parcial? Un espectro
- Autonomía total: el agente fija sus propios objetivos y actúa sin supervisión ni puertas de aprobación. Suena potente, pero para acciones con consecuencias es raro y arriesgado: casi nadie opera así.
- Autonomía parcial (lo habitual): el agente es autónomo dentro de unos límites: un catálogo de herramientas acotado, solo lectura o con aprobación humana para lo sensible, un máximo de pasos, y human-in-the-loop para lo irreversible. Es “autonomía con guardarraíles”.
Como casi todo en este curso, no es un interruptor sino un espectro: la ingeniería consiste en subir la autonomía solo donde el riesgo lo permite.
Qué factores afectan a la gobernanza y a la explicabilidad
Cuanta más autonomía, más difícil de gobernar y de explicar. Los factores que más pesan:
- Capacidad de actuar (leer vs escribir): un agente que solo lee es fácil de tolerar; uno que modifica o envía necesita controles fuertes. La acción irreversible es la línea roja.
- Iniciativa propia: un agente que actúa solo (programado o por eventos) puede hacer cosas sin que nadie esté mirando → riesgo de “acciones en la sombra”.
- Cadena larga sin supervisión: cuantos más pasos encadena, más difícil es reconstruir por qué hizo algo; la explicabilidad cae.
- Amplitud de herramientas y datos: más acceso = mayor “radio de impacto” si se equivoca.
- No determinismo: el modelo decide de forma probabilística, así que ante la misma entrada puede actuar distinto → auditar y reproducir se complica.
La defensa es la misma que recorre el curso: “el prompt sugiere, el código garantiza”. La autonomía segura no se logra confiando en el modelo, sino registrando cada decisión (qué herramienta, con qué argumentos y con qué resultado), acotando el catálogo de herramientas y poniendo puertas de aprobación en las acciones con consecuencias. Esto enlaza directamente con la sección de gobernanza.
Un ejemplo: del agente reactivo al “vigilante de expedientes”
El agente de expedientes (sección 8) es reactivo: alguien pregunta “¿qué expedientes están abiertos?” y responde. Una versión autónoma sería un vigilante que nadie tiene que invocar:
- Iniciativa (proactiva): se ejecuta solo, p. ej. cada mañana (por un cron), sin que nadie pregunte.
- Meta, no tarea: su objetivo es “avisar de expedientes que llevan demasiado tiempo abiertos”; él decide qué consultar.
- Cadena de pasos: consulta la fecha de hoy (
fecha_y_hora), busca los expedientes abiertos (buscar_expedientes), calcula cuántos días llevan (calcular) y selecciona los que superan un umbral. - Guardarraíles (autonomía parcial): solo lee; el resultado es un borrador de aviso, no un envío automático. Una persona lo revisa antes de que salga. El catálogo no incluye ninguna herramienta de escritura.
Lo relevante es lo que cambia y lo que no: el mecanismo es idéntico (LLM + herramientas + bucle), pero ahora el agente arranca solo, persigue una meta y encadena varias herramientas. Lo que lo mantiene seguro no es el modelo, sino el diseño: lectura sin escritura, umbral explícito y una persona en la puerta de salida. Dar un escalón más (que envíe el aviso por sí mismo) exigiría, como mínimo, una puerta de aprobación y registro completo de cada acción.
Explicabilidad: registrar cada decisión (herramienta, argumentos y resultado) es lo único que permite responder "¿por qué actuó así?" cuando nadie estaba mirando.
13. La otra ruta: Low Code / No Code
Nota: este apartado es conceptual. El espacio se mueve muy rápido (nombres, licencias y features cambian cada pocos meses); las herramientas listadas son orientativas a inicio de 2026.
Todo lo anterior (01–09) es construcción de agentes por desarrolladores (“pro-code”): cada comportamiento es código explícito, versionable en git, con control total. Existe una ruta paralela pensada para usuarios no técnicos.
Qué es (en contraste con lo anterior)
- Pro-code (el camino de este curso): se escribe código. Control y transparencia totales; requiere saber programar.
- Low-code: construyes el agente visualmente (cajas que conectas: modelo, herramientas, memoria, condiciones) con poca o ninguna programación. Público: “citizen developers” / expertos de negocio.
- No-code: cero programación. Describes el agente en lenguaje natural o rellenas formularios y la plataforma lo genera.
Es el mismo espectro de la sección 11, extendido:
flowchart LR
A["Código a mano
(01–08)"] --> B["Framework
(LangGraph, 09)"] --> C["Low-code visual
(arrastrar y soltar)"] --> D["No-code
(describir y listo)"]
De izquierda a derecha: más control y transparencia → más accesibilidad para perfiles no técnicos.
Clave conceptual: un constructor visual de agentes es, casi siempre, una
interfaz gráfica sobre el mismo grafo de la sección 10. Arrastrar una caja “LLM”
y conectarla a una caja “Tool” es dibujar el mismo StateGraph. No hay magia nueva
debajo: hay el mismo bucle LLM + herramientas.
Por qué se introduce este paradigma
- Escasez de desarrolladores y cuello de botella de IT: negocio quiere agentes más rápido de lo que IT puede entregarlos.
- Democratización: el experto de dominio (que conoce el proceso mejor que el dev) construye directamente, sin intermediarios.
- Velocidad / prototipado: de la idea al agente en horas.
- El boom de los agentes: todos quieren agentes; los fabricantes lo empaquetan para vender a un público mucho más amplio que los programadores.
- Coste aparente: menos horas de desarrollo (con matices, ver contras).
Ventajas
Técnicas: desarrollo rápido; depuración visual; conectores pre-construidos; infraestructura incluida (memoria, logs, despliegue); menos boilerplate. Organizativas: menos dependencia de IT; el experto de dominio itera solo; time-to-market corto.
Inconvenientes técnicos
- Abstracción con techo: funciona hasta que pides algo que la plataforma no previó; entonces te chocas con un muro y no hay “bajar a código”.
- Difícil de versionar/testear/revisar: el “programa” es un grafo en una base de datos, no código en git → mal diff, mal code review, mal CI/CD, rollback complicado.
- Observabilidad y depuración limitadas: cuando falla, el usuario no técnico no puede diagnosticar.
- Opacidad: cuesta saber qué hace exactamente el agente por debajo.
Inconvenientes de soberanía digital
Aquí está el núcleo para este proyecto:
- Vendor lock-in: la lógica de negocio vive dentro de la plataforma; la exportación suele ser parcial o nula. Migrar = rehacer.
- Residencia de datos: en las plataformas SaaS, los prompts, los datos (y a veces el acceso a la BD) salen a la nube del proveedor.
- Dependencia del roadmap ajeno: precios, disponibilidad, cambios de API y la propia supervivencia del proveedor escapan a el control.
- Pérdida de control del modelo: qué LLM se usa y dónde corre lo decide la plataforma, no quien crea el agente.
- La trampa de la consultoría: el low-code complejo a menudo sí requiere partners/consultores certificados → reaparece justo la dependencia que se quería evitar.
Matiz importante: no todo low-code es SaaS propietario. Los constructores visuales OSS autoalojables (n8n, Flowise, Langflow, Dify) mitigan buena parte de lo anterior (corren on-premise, sin nube ajena, conectan a Ollama) pero no todo: siguen teniendo el techo de abstracción y el problema de “grafo-en-BD vs código-en-git”.
Catálogo de herramientas (por categoría y soberanía)
A) Low-code visual, OSS y autoalojable (compatible con soberanía + Ollama): - n8n: automatización + nodos de IA/agente (sobre LangChain). Muchísimos conectores. - Flowise: builder visual centrado en agentes/LLM (sobre LangChain). - Langflow: builder visual sobre LangChain (mantenido por DataStax). - Dify: plataforma LLMOps con builder de workflows/agentes. - Rivet, Botpress (núcleo OSS): orientados a flujos conversacionales.
B) Low-code / no-code SaaS propietario (NO encaja con soberanía estricta): - Microsoft Copilot Studio (Power Platform): low-code empresarial. - OpenAI GPTs / Assistants: no-code conversacional. - Google Vertex AI Agent Builder / Dialogflow CX: low-code en GCP. - Amazon Bedrock Agents: low-code en AWS. - Salesforce Agentforce: agentes dentro del CRM. - Lindy, Relevance AI, Stack AI, Voiceflow, Zapier Agents, Make: builders SaaS.
C) RPA empresarial con capa agéntica (propietario, alto coste): - UiPath, Automation Anywhere: automatización robótica + IA.
D) “Declarativo para devs” (punto medio: config en vez de GUI, pero aún técnico, no es para usuarios finales, lo incluyo por completitud): - CrewAI, Microsoft AutoGen / AG2, OpenAI Agents SDK, Semantic Kernel: se definen roles/agentes en Python o YAML, más declarativo que el código a mano pero lejos del no-code visual.
La conclusión incómoda: la mayor parte del mercado “no-code” es SaaS propietario en la nube (lo que la soberanía estricta descarta). El nicho que encaja es la categoría A (OSS autoalojable).
14. Gobernanza cuando los usuarios crean agentes
El low-code no es solo una decisión técnica: al dar a usuarios no técnicos la capacidad de crear agentes, abres un problema de gobernanza de IA que el pro-code no tiene (porque ahí el filtro es “saber programar” + code review).
El riesgo nuevo: “Shadow AI” y proliferación
Si cualquiera puede crear agentes, aparecen decenas o cientos fuera del control de IT/seguridad. Riesgos concretos:
- Fuga de datos: un agente creado por un usuario conecta datos sensibles a un LLM en la nube sin que nadie lo revise.
- Seguridad inconsistente: credenciales a fuego, permisos de herramientas demasiado amplios, sin revisión.
- Cumplimiento: RGPD, regulación sectorial (en sectores muy supervisados el listón es altísimo), y el EU AI Act (clasificación por riesgo, obligaciones de documentación y supervisión humana). ¿Quién responde de lo que decide un agente hecho por un usuario?
- Calidad: un usuario no técnico no sabe evaluar alucinaciones ni inyección de prompts.
- Sprawl sin inventario: nadie sabe cuántos agentes hay, quién los posee, ni a qué datos acceden.
Cómo se gobierna (los mecanismos)
No hay una bala de plata; es una combinación de capas:
- Guardarraíles en la plataforma: las plataformas empresariales incluyen controles de admin: quién puede publicar, qué conectores/datos se permiten, políticas DLP, entornos separados (dev/test/prod), flujos de aprobación. (Esto es, de hecho, un argumento de venta del SaaS propietario… y una razón por la que las organizaciones aceptan el lock-in.)
- Catálogo / inventario de agentes: un registro de qué agentes existen, quién los posee, qué datos y herramientas usan. Sin inventario no hay gobierno.
- Acceso por niveles + puerta de promoción: los usuarios construyen en sandbox; pasar a producción exige revisión de IT/seguridad. Es el equivalente al code review del pro-code.
- Guardarraíles en herramientas y datos: restringir qué tools y qué fuentes de datos se exponen. Aquí conecta con todo el curso →
- Política, aprobación y human-in-the-loop: firma humana para acciones sensibles, gestión del cambio.
- Auditoría y observabilidad: registro de las acciones del agente, evaluaciones, monitorización continua.
- Centro de Excelencia (CoE): patrón habitual, un equipo central fija estándares, plantillas y guardarraíles, forma a los citizen developers y cura los componentes aprobados.
La paradoja de la gobernanza
Cuanto más se democratiza la creación de agentes, más hay que invertir en guardarraíles centralizados. La agilidad que se gana por un lado se paga en superficie de gobierno por otro. No es gratis.
Conexión con lo construido (la síntesis del curso)
El enfoque seguido es en sí mismo un mecanismo de gobernanza:
- “El prompt sugiere, el código garantiza” (sección 8): los guardarraíles viven en el código y en las herramientas, no en confiar en lo que escriba el usuario que construye el agente.
- Un catálogo de herramientas curado (servidores MCP, sección 9) es una palanca de gobierno: el equipo central posee las herramientas (consultas parametrizadas, datos acotados, solo lectura) y cualquier agente (lo monte un dev o un usuario en una GUI low-code) solo puede hacer lo que esas herramientas permiten. Gobiernas en la capa de tools, no en la capa de quién construye.
Para un contexto de alto riesgo (muy regulado), esto sugiere una postura clara: permitir a los usuarios construir agentes, pero solo sobre un catálogo de herramientas vetado y controlado centralmente (idealmente vía MCP on-premise). Se obtiene la agilidad del low-code sin ceder el control de lo que los agentes pueden tocar.
15. El reverso: por qué seguir con el curso filosófico
Llegados a este punto, el agente está construido y, en cada ejemplo, asentado sobre dos pivotes que dan gobernanza y explicabilidad. ¿Misión cumplida? Pues no: lo que el código no puede darse a sí mismo queda fuera, y ese hueco tiene un curso propio que actúa como reverso filosófico de este y que no es simplemente una capa de ética decorativa.
Que la cuestión no es cosa de filósofos de salón lo reconoce quien construye estos sistemas: en mayo de 2026, al presentarse la encíclica Magnifica humanitas, hasta un cofundador de Anthropic lo dijo ante el Vaticano: que las preguntas sobre cómo debería actuar la IA exceden con mucho lo que un ingeniero puede resolver, y competen a las humanidades, la filosofía y la sociedad.
Las razones para seguir son de ingeniería:
- Hay un tercer pivote que el código no puede darse a sí mismo. Aquí se protege al operador y a la organización; queda fuera la cuenta que se le debe al afectado por una decisión del agente (su voz, su vía de recurso). No es un descuido: una norma de responsabilidad, una jurisdicción, un derecho a impugnar son, por construcción, externos al repositorio. Reconocer lo que un sistema estructuralmente no puede darse a sí mismo es madurez de ingeniería, no moralina.
- Las decisiones de arquitectura ya son políticas, se piensen o no. “El código
es ley”: elegir on-premise frente a la nube, qué herramientas expone el catálogo,
dónde va el
max_turnos… reparte poder y regula conducta mejor que una norma escrita, pero sin votación ni recurso. Más vale tomarlas con criterio que por inercia. - Los problemas que vienen son los que ese curso nombra con precisión. El “problema de las muchas manos” (¿quién responde cuando un comité de modelos “decidió” y, en realidad, no decidió nadie?), la brecha de responsabilidad, la irreversibilidad como línea roja. No son abstracciones: son problemas de diseño con consecuencias de producto y legales (el EU AI Act, entre otras) que acaban en el backlog.
- Es rigor, no brocha gorda. No dice “la IA es mala”: practica el mismo descenso al artefacto concreto que este curso al medir el sesgo de un modelo en vez de despachar “la IA”. Se lee contra este curso, sección a sección, con un mapeo 1:1 (cada caja ⚖️ tiene su contraparte 🔗).
- Mejora el criterio de quien diseña. Anticipar “¿quién responde ante el afectado?” antes de que la planteen un regulador, un cliente o un juez es, exactamente, diseñar para que el sistema aguante el escrutinio. La crítica que solo diagnostica llega tarde; esta, apoyada en el artefacto, llega a tiempo.
En una línea: la teoría sin el artefacto es el lujo de quien no construye; el artefacto sin la teoría es un poder sin pregunta. El artefacto ya está construido (el reverso es la pregunta).
→ Curso de implicaciones filosóficas, sociales y políticas de los agentes
