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:

  1. El mecanismo desnudo: cómo el modelo “pide” una herramienta y el código la ejecuta.
  2. Búsqueda web: dar al modelo acceso a internet, primero con un buscador público y luego con uno propio y soberano.
  3. Un agente de verdad: el bucle que deja al modelo decidir cuántas herramientas usar y cuándo parar.
  4. Diseño de herramientas: por qué la descripción de una tool decide si el agente acierta.
  5. Datos reales: conectar el agente a una base de datos sin que el modelo escriba SQL.
  6. Las tres arquitecturas: el mismo agente a mano, con MCP y con LangGraph.
  7. 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):

Probado en


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:

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:

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
        },
    },
}

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:

  1. El modelo recibe system + user + TOOLS. Según el system, ante una pregunta sobre un dato actual, debe buscar.
  2. Emite una llamada, no una respuesta: buscar_web(query="..."). Se detiene y espera (el modelo no tiene acceso a internet).
  3. El código detecta la llamada, busca la función en FUNCIONES, la ejecuta (consulta a SearXNG) y obtiene una lista de resultados.
  4. El código añade el resultado como mensaje role: "tool" y vuelve a llamar al modelo con toda la conversación.
  5. El modelo lee los resultados y, siguiendo el system (“lee la fuente”), emite otra llamada: leer_pagina(url=...) con la fuente más fiable.
  6. El código ejecuta y devuelve el texto real de la página.
  7. Si necesita confirmar algún dato, el modelo puede emitir más llamadas (otra búsqueda, otra página…), repitiendo el ciclo.
  8. El código ejecuta cada una y le devuelve el contenido.
  9. Cuando ya tiene lo que necesita, el modelo responde con texto normal (sin llamadas), citando las fuentes usadas.
  10. 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 y explicabilidad Gobernanza: el modelo solo decide; quien ejecuta, valida y limita (qué funciones existen, cuántos turnos) es el código. El control vive en el código, no en el prompt.
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


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:

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):

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_webleer_pagina
Años desde un año dado fecha_y_horacalcular

Lo que demuestra

  1. 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 description de cada herramienta + el system prompt.
  2. 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.
  3. Busca + verifica (pregunta 3): mantiene el patrón de leer la fuente oficial antes de afirmar.

Los límites de usar un modelo 14B

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.

⚖️ Gobernanza y explicabilidad Gobernanza: el agente solo puede usar las herramientas del catálogo; ampliar o restringir lo que es capaz de hacer es añadir o quitar herramientas, de forma explícita.
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:

  1. 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.
  2. Palabras-disparador que no se pisan: “cálculo numérico” vs “fecha/hora” vs “internet” vs “tras buscar”. Sin solape no hay duda.
  3. SIEMPRE / mayúsculas para vencer un instinto del modelo: en calcular se 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

  1. 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.
  2. Llamadas redundantes: llamó a dos herramientas donde bastaba una (“por si acaso”), gastando tiempo y tokens.
  3. Herramienta muerta: buscar_general no 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.

⚖️ Gobernanza y explicabilidad Gobernanza: un catálogo con descripciones que se solapan vuelve el comportamiento impredecible; diseñar herramientas sin solapes es, en sí, una medida de control.
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

  1. Lenguaje natural → filtros estructurados: “incidentes abiertos” se convirtió en tipo='incidente', estado='abierto'. Los enum del esquema le dieron al modelo el vocabulario exacto de los datos.
  2. 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.
  3. Enruta bien entre las dos herramientas: para un código concreto usó detalle_expediente, no buscar_expedientes.

Dos blindajes clave para producción

Una capa así (solo lectura, consultas acotadas) es justo la fachada segura que encaja en una modernización strangler sobre Oracle.

⚖️ Gobernanza y explicabilidad Gobernanza: el modelo no escribe SQL; solo invoca consultas parametrizadas y de solo lectura sobre un catálogo acotado → radio de impacto mínimo.
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:

@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)

  1. 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] inputSchema ya es JSON Schema estándar → encaja directo en el formato de Ollama.
  2. 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)

⚖️ Gobernanza y explicabilidad Gobernanza: las herramientas viven en un servidor central y curado; un equipo controla qué pueden hacer todos los agentes, en vez de cada agente por su cuenta. Es una palanca de gobierno de primer orden.
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.

⚖️ Gobernanza y explicabilidad Gobernanza: el framework trae puntos de control listos (checkpoints, human-in-the-loop) para exigir aprobación humana en pasos sensibles.
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)

  1. Aprender y prototipar: solo código (lo hecho en el curso). Transparente y sin ataduras.
  2. Compartir la capa de expedientes entre varios agentes/equipos: MCP (fino, abierto, on-premise, encaja con la modernización strangler).
  3. 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:

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

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:

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:

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.

⚖️ Gobernanza y explicabilidad Gobernanza: a más autonomía (iniciativa propia, escritura, cadenas largas), más controles imprescindibles: catálogo acotado, solo lectura y puertas de aprobación para lo irreversible.
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)

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

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

Inconvenientes de soberanía digital

Aquí está el núcleo para este proyecto:

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:

Cómo se gobierna (los mecanismos)

No hay una bala de plata; es una combinación de capas:

  1. 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.)
  2. 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.
  3. 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.
  4. Guardarraíles en herramientas y datos: restringir qué tools y qué fuentes de datos se exponen. Aquí conecta con todo el curso →
  5. Política, aprobación y human-in-the-loop: firma humana para acciones sensibles, gestión del cambio.
  6. Auditoría y observabilidad: registro de las acciones del agente, evaluaciones, monitorización continua.
  7. 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:

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:

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