El módulo 4 terminó con una frontera dibujada con precisión: DocuBot sabe — responde con fuentes sobre todo lo que dice la documentación de Nubelia — pero no puede hacer. La pregunta que dejamos en el aire, "¿cuántas tareas tiene ahora mismo mi proyecto Atlas?", no tiene respuesta en ninguna wiki: el dato vive en la API de Nubelia, cambia cada minuto, y ningún pipeline RAG del mundo lo va a recuperar de una colección de chunks. En esta lección cruzamos esa frontera con el mecanismo llamado function calling (o tool use): le describimos al modelo qué funciones tenemos, el modelo decide cuándo y con qué argumentos conviene llamarlas, y nuestro código las ejecuta y le devuelve el resultado. Es probablemente la idea con peor nombre de todo el ecosistema — el modelo no llama a ninguna función — y a la vez una de las más transformadoras: es la pieza que separa un chatbot de un asistente que opera sobre tu sistema. Aquí construiremos la primera herramienta de DocuBot, consultar_proyecto, y cerraremos por fin la pregunta de Atlas.
Contenido
- La idea clave: el modelo decide, tu código ejecuta
- Definir una herramienta: JSON Schema y descripciones que son prompt engineering
- El flujo completo con el SDK:
tool_use→ ejecutar →tool_result→ respuesta final - La primera herramienta de DocuBot:
consultar_proyectocontra la API de Nubelia - Cuando la herramienta falla: errores como
tool_resultconis_error tool_choice: elección automática, forzada y prohibida- Function calling como evolución de "pedir JSON" (02-03)
La idea clave: el modelo decide, tu código ejecuta
Desmontemos primero el malentendido que da nombre a la técnica. En function calling el LLM no ejecuta absolutamente nada: no tiene acceso a tu red, ni a tu base de datos, ni a la API de Nubelia. Lo único que hace el modelo es lo que siempre ha hecho — generar tokens —, solo que ahora puede generar un tipo especial de salida estructurada que significa "quiero que llames a esta función con estos argumentos". Tu aplicación lee esa petición, decide si la atiende, ejecuta la función con código Python normal y corriente, y le devuelve el resultado al modelo en un segundo turno para que redacte la respuesta final.
sequenceDiagram
participant U as Usuario
participant A as Tu aplicación (Python)
participant L as LLM (API)
participant N as API de Nubelia
U->>A: "¿Cuántas tareas tiene mi proyecto Atlas?"
A->>L: pregunta + definiciones de herramientas
L-->>A: tool_use: consultar_proyecto({"nombre": "Atlas"})
Note over L: El modelo NO ejecuta nada.<br/>Solo pide, con argumentos estructurados.
A->>N: obtener_proyecto("Atlas") — código tuyo
N-->>A: {"tareas_abiertas": 27, ...}
A->>L: tool_result con ese JSON
L-->>A: "Tu proyecto Atlas tiene 27 tareas abiertas..."
A-->>U: respuesta final en lenguaje naturalEsta división de trabajo tiene consecuencias importantes, todas buenas:
- El control es tuyo. Entre el
tool_usey la ejecución hay una línea de código Python que escribes tú. Puedes validar los argumentos, aplicar permisos, registrar la llamada o negarte a ejecutarla. El modelo propone; tu código dispone. - La seguridad se razona como siempre. La función que ejecutas es código tuyo con las credenciales de tu aplicación — le aplican los mismos principios de toda la vida, empezando por el de mínimo privilegio: una herramienta de consulta debe usar credenciales de solo lectura.
consultar_proyectono puede borrar nada porque el código que la implementa no sabe borrar nada. (Las acciones de escritura y sus salvaguardas llegan en 05-02, y los ataques que explotan herramientas, en 06-01.) - El modelo aporta lo que ningún
ifpuede: entender que "¿cómo va Atlas?", "dime el estado de mi proyecto Atlas" y "how many open tasks in Atlas?" son la misma intención, y traducirla a una llamada con argumentos correctos.
Definir una herramienta: JSON Schema y descripciones que son prompt engineering
Para que el modelo pueda pedir una función, primero hay que describírsela. Cada herramienta se define con tres piezas:
| Campo | Qué es | Quién lo "lee" |
|---|---|---|
name |
Identificador de la función (letras, números, guiones bajos) | Tu código, para saber qué ejecutar |
description |
Explicación en lenguaje natural de qué hace, cuándo usarla y cuándo no | El modelo — es la pieza más importante |
input_schema |
JSON Schema de los argumentos: tipos, campos obligatorios, enums | El modelo (para generar argumentos válidos) y la API (que los valida) |
La description merece que nos detengamos: es prompt engineering. Todo lo aprendido en el módulo 2 aplica aquí — el modelo decidirá si usa la herramienta, y con qué argumentos, basándose casi exclusivamente en ese texto. Compara:
# Descripción pobre: el modelo tendrá que adivinar
{"description": "Consulta un proyecto"}
# Descripción trabajada: cuándo usarla, qué devuelve, qué NO hace
{"description": (
"Consulta el estado EN TIEMPO REAL de un proyecto del usuario en Nubelia: "
"tareas abiertas y completadas, miembros del equipo y fecha límite. "
"Úsala cuando el usuario pregunte por datos actuales de un proyecto concreto "
"('¿cuántas tareas tiene Atlas?', '¿cómo va mi proyecto Boreal?'). "
"NO la uses para preguntas sobre cómo funciona Nubelia (planes, permisos, "
"integraciones): eso se responde con la documentación."
)}La segunda versión hace tres cosas que la primera no: delimita el cuándo sí con ejemplos (few-shot en miniatura, 02-02), delimita el cuándo no (evita que el modelo llame a la API para preguntas de documentación), y anuncia qué devuelve (el modelo redactará mejor la respuesta final sabiendo qué esperar).
El flujo completo con el SDK: tool_use → ejecutar → tool_result → respuesta final
Veamos el mecanismo con el SDK anthropic que usamos desde 03-01 — el flujo es conceptualmente idéntico en OpenAI, Google o Mistral (cambian los nombres de campo: tools/tool_calls/function, pero el baile es el mismo). Los pasos:
- Primera llamada: envías la pregunta y la lista de herramientas (parámetro
tools). - Si el modelo decide usar una, la respuesta llega con
stop_reason == "tool_use"y un bloque de contenido de tipotool_useconname,input(los argumentos, ya como dict) y unidde correlación. - Tu código ejecuta la función correspondiente.
- Segunda llamada: reenvías la conversación completa — incluida la respuesta del asistente con su bloque
tool_use— y añades un mensaje de usuario con un bloquetool_resultque referencia aquelid. - El modelo, ya con el dato en la mano, redacta la respuesta final (
stop_reason == "end_turn").
Los puntos 2 y 4 esconden los dos detalles que más errores causan: el bloque tool_use del asistente debe volver en el historial (si no, el modelo no sabe qué pidió), y el tool_result va dentro de un mensaje con rol user — desde el punto de vista de la conversación, el resultado de la herramienta es algo que "le cuentas" al modelo, y debe ser el primer contenido de ese mensaje.
La primera herramienta de DocuBot: consultar_proyecto contra la API de Nubelia
Vamos a construirlo de verdad. Como en este curso no tenemos una API de Nubelia desplegada, la simulamos con un módulo mock — la gracia didáctica es que al modelo le da exactamente igual: él solo ve la definición de la herramienta y los resultados; que detrás haya un dict o un microservicio con base de datos es invisible desde su lado de la frontera.
# api_nubelia.py — simulación de la API interna de Nubelia
# En producción, estas funciones harían llamadas HTTP autenticadas
# (con credenciales de SOLO LECTURA: mínimo privilegio).
class ProyectoNoEncontrado(Exception):
pass
_PROYECTOS = {
"atlas": {"nombre": "Atlas", "estado": "activo", "tareas_abiertas": 27,
"tareas_completadas": 143, "miembros": 6, "fecha_limite": "2026-09-30"},
"boreal": {"nombre": "Boreal", "estado": "activo", "tareas_abiertas": 4,
"tareas_completadas": 89, "miembros": 3, "fecha_limite": "2026-07-15"},
"cierzo": {"nombre": "Cierzo", "estado": "pausado", "tareas_abiertas": 12,
"tareas_completadas": 51, "miembros": 4, "fecha_limite": None},
}
def obtener_proyecto(nombre: str) -> dict:
"""Devuelve el estado actual de un proyecto. Lanza ProyectoNoEncontrado si no existe."""
clave = nombre.strip().lower()
if clave not in _PROYECTOS:
disponibles = ", ".join(p["nombre"] for p in _PROYECTOS.values())
raise ProyectoNoEncontrado(
f"No existe ningún proyecto llamado '{nombre}'. Proyectos disponibles: {disponibles}."
)
return _PROYECTOS[clave]Ahora la definición de la herramienta y su despachador, en un módulo nuevo que crecerá durante el módulo:
# herramientas.py — definiciones de herramientas de DocuBot y su ejecución
import api_nubelia
HERRAMIENTAS = [
{
"name": "consultar_proyecto",
"description": (
"Consulta el estado EN TIEMPO REAL de un proyecto del usuario en Nubelia: "
"tareas abiertas y completadas, miembros y fecha límite. Úsala cuando el "
"usuario pregunte por datos actuales de un proyecto concreto. NO la uses "
"para preguntas sobre el funcionamiento de Nubelia (documentación)."
),
"input_schema": {
"type": "object",
"properties": {
"nombre": {
"type": "string",
"description": "Nombre del proyecto tal y como lo menciona el usuario, p. ej. 'Atlas'.",
},
},
"required": ["nombre"],
},
},
]
def ejecutar_herramienta(nombre: str, argumentos: dict) -> dict:
"""Puente entre lo que el modelo pide y el código que lo hace."""
if nombre == "consultar_proyecto":
return api_nubelia.obtener_proyecto(argumentos["nombre"])
raise ValueError(f"Herramienta desconocida: {nombre}")Y el flujo completo. Fíjate en que reutilizamos llamar_con_reintentos (03-02) y MODELO (03-01) sin tocarlos — las herramientas son un parámetro más de la llamada. Lo que sí necesitamos es un sistema nuevo: el SYSTEM_DOCUBOT de 02-01 ordena basarse exclusivamente en el bloque <documentacion>, y esa regla, perfecta para RAG, dejaría a la herramienta sin uso. Añadimos a prompts.py una variante (y subimos PROMPT_VERSION, como manda 02-04):
# prompts.py (añadido en 05-01) — versión del sistema consciente de herramientas SYSTEM_DOCUBOT_HERRAMIENTAS = """Eres DocuBot, el asistente interno de Nubelia. Solo respondes sobre Nubelia. Para datos en tiempo real de los proyectos del usuario, usa las herramientas disponibles. Nunca inventes datos de proyectos: si una herramienta falla o no encuentra algo, explica el problema con claridad. Responde en el idioma del usuario, con tono profesional y conciso."""
# docubot_herramientas.py — el flujo tool_use -> ejecutar -> tool_result
import json
from cliente import MODELO, extraer_texto
from reintentos import llamar_con_reintentos
from prompts import SYSTEM_DOCUBOT_HERRAMIENTAS
from herramientas import HERRAMIENTAS, ejecutar_herramienta
def responder_con_herramienta(pregunta: str) -> str:
messages = [{"role": "user", "content": pregunta}]
# 1) Primera llamada: pregunta + herramientas disponibles
respuesta = llamar_con_reintentos(
model=MODELO,
max_tokens=1024,
system=SYSTEM_DOCUBOT_HERRAMIENTAS,
tools=HERRAMIENTAS,
messages=messages,
)
# ¿El modelo respondió directamente, sin necesitar herramienta?
if respuesta.stop_reason != "tool_use":
return extraer_texto(respuesta)
# 2) El modelo pidió una herramienta: localizar el bloque tool_use
bloque = next(b for b in respuesta.content if b.type == "tool_use")
print(f"[tool_use] {bloque.name}({bloque.input})") # traza mínima, por ahora
# 3) EJECUTAR — esto es Python nuestro, no el modelo
try:
resultado = ejecutar_herramienta(bloque.name, bloque.input)
contenido = json.dumps(resultado, ensure_ascii=False)
es_error = False
except Exception as e:
contenido = str(e) # el error, contado en texto
es_error = True
# 4) Segunda llamada: historial completo + tool_result correlacionado por id
messages.append({"role": "assistant", "content": respuesta.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": bloque.id,
"content": contenido,
"is_error": es_error,
}],
})
final = llamar_con_reintentos(
model=MODELO, max_tokens=1024,
system=SYSTEM_DOCUBOT_HERRAMIENTAS,
tools=HERRAMIENTAS, messages=messages,
)
return extraer_texto(final)
print(responder_con_herramienta("¿Cuántas tareas tiene ahora mismo mi proyecto Atlas?"))Una ejecución típica:
[tool_use] consultar_proyecto({'nombre': 'Atlas'})
Tu proyecto Atlas tiene actualmente 27 tareas abiertas (y 143 completadas).
El proyecto está activo, con 6 miembros y fecha límite el 30 de septiembre de 2026.La pregunta que en 01-04 nos servía de ejemplo de lo imposible acaba de tener respuesta — y con el dato real del sistema, no con una alucinación plausible. Tres observaciones antes de seguir:
bloque.inputllega como dict ya parseado por el SDK. Aun así, trátalo como entrada no confiable: el modelo puede pasar un nombre de proyecto que no existe o un valor inesperado. Nuestra defensa está enobtener_proyecto(normaliza y valida), no en confiar en el modelo.- El resultado viaja como texto (por eso el
json.dumps): untool_resultes contenido para el modelo, y JSON compacto y con claves descriptivas es un formato que entiende de maravilla. No hace falta prosa. - Este flujo resuelve un paso de herramienta y termina. Si el modelo necesitara encadenar varias llamadas (consultar la documentación y luego la API), haría falta un bucle — exactamente el tema de 05-02, no lo adelantemos.
Cuando la herramienta falla: errores como tool_result con is_error
Ya viste el try/except en el código anterior; ahora la filosofía. Cuando una herramienta falla, la peor opción es dejar que la excepción reviente el flujo, y la segunda peor es devolver al usuario un traceback. La opción buena es la que implementamos: devolver el error al modelo como tool_result con is_error: True, con un mensaje de error informativo — y dejar que el modelo haga lo que mejor sabe: explicarlo en lenguaje natural.
Pregunta: "¿cómo va mi proyecto Zafiro?". La herramienta lanza ProyectoNoEncontrado con nuestro mensaje ("No existe ningún proyecto llamado 'Zafiro'. Proyectos disponibles: Atlas, Boreal, Cierzo."), y el modelo responde algo como:
No encuentro ningún proyecto llamado "Zafiro" en tu cuenta de Nubelia. Tus proyectos actuales son Atlas, Boreal y Cierzo — ¿te referías a alguno de ellos?
Fíjate en la cadena de valor: el mensaje de la excepción también es prompt engineering. Como incluimos la lista de proyectos disponibles en el error, el modelo puede ofrecer alternativas en vez de un "no existe" seco. Escribe los errores de tus herramientas pensando en que su lector es un LLM que va a re-explicarlos: qué falló, por qué, y qué se puede hacer. Y la misma regla de siempre: en el mensaje de error, nada de secretos ni detalles internos (rutas, SQL, stack traces) — irían directos al contexto del modelo y, potencialmente, a la respuesta del usuario.
tool_choice: elección automática, forzada y prohibida
Por defecto el modelo decide libremente si usa una herramienta o responde directamente. El parámetro tool_choice te da el mando cuando lo necesitas:
| Valor | Comportamiento | Cuándo usarlo |
|---|---|---|
{"type": "auto"} |
El modelo decide (defecto con tools) |
Asistentes como DocuBot: unas preguntas necesitan herramienta y otras no |
{"type": "any"} |
Obligado a usar alguna herramienta | Routers puros: la respuesta directa no es una opción válida |
{"type": "tool", "name": "..."} |
Obligado a usar esa herramienta | Extracción estructurada: quieres los argumentos, no una conversación |
{"type": "none"} |
Prohibido usar herramientas | Forzar una respuesta de cierre con las herramientas aún declaradas |
El tercer caso es más útil de lo que parece y conecta directamente con la siguiente sección: si fuerzas una herramienta concreta, el modelo siempre genera un bloque tool_use con argumentos que cumplen tu input_schema. Es decir: acabas de conseguir salida estructurada garantizada.
Function calling como evolución de "pedir JSON" (02-03)
En 02-03 montamos el contrato JSON de DocuBot pidiendo el formato en el prompt y defendiéndonos con limpiar_json y validar_contrato — el modelo a veces envolvía el JSON en prosa o en vallas de código, y había que limpiarlo. Function calling es la versión industrializada de aquella idea:
| Aspecto | Pedir JSON en el prompt (02-03) | Function calling |
|---|---|---|
| Cómo se especifica el formato | Prosa + ejemplo en el prompt | input_schema formal (JSON Schema) |
| Fiabilidad sintáctica | Alta pero no garantizada (de ahí limpiar_json) |
Muy alta: el modelo está entrenado específicamente para emitir argumentos conformes al esquema |
| "Charla" alrededor del JSON | Frecuente sin instrucciones férreas | No existe: el bloque tool_use es estructura pura |
| Tipos y enums | Se piden por favor | Se declaran en el esquema |
| Validación semántica (valores con sentido) | Tuya (validar_contrato) |
Sigue siendo tuya |
La última fila es la moraleja: function calling elimina la fontanería sintáctica, pero la validación defensiva de valores no se jubila — que {"nombre": "Zafiro"} sea JSON perfecto no hace que Zafiro exista. El instinto de 02-03 ("no confíes, valida") sigue siendo la ley; solo ha cambiado dónde aplicarlo.
Errores Comunes y Consejos
- Olvidar reenviar el turno del asistente. El error más frecuente del flujo: mandar el
tool_resultsin haber añadido antes{"role": "assistant", "content": respuesta.content}al historial. La API rechazará la petición — eltool_resultreferencia untool_use_idque, sin ese mensaje, no existe en la conversación. - Descripciones de herramienta escuálidas. "Consulta un proyecto" produce llamadas de más (para preguntas de documentación) y de menos (para formulaciones indirectas). Trata la
descriptioncon el mismo rigor que tratasteSYSTEM_DOCUBOT: cuándo sí, cuándo no, ejemplos. - Creer que el esquema valida por ti. El
input_schemagarantiza forma, no verdad. Valida los valores en tu función (¿existe el proyecto?, ¿el rango es razonable?) como si los hubiera tecleado un desconocido — porque, en cierto modo, así es. - No contemplar el camino sin herramienta. Con
tool_choice: auto, el modelo puede responder directamente (stop_reason == "end_turn"). Si tu código asume que siempre habrá un bloquetool_use, esenext(...)lanzaráStopIterationen la primera pregunta trivial. Comprueba siemprestop_reasonprimero. - Varios
tool_useen un mismo turno. El modelo puede pedir más de una herramienta a la vez (llamadas paralelas). Nuestro flujo de hoy procesa una porque su única herramienta no da para más; el bucle de 05-02 iterará sobre todos los bloques. Si te ocurre antes, responde con untool_resultpor cadatool_use_id. - Errores de herramienta que no ayudan a nadie.
KeyError: 'zafiro'comotool_resultobliga al modelo a improvisar. Escribe errores con contexto y alternativas — y sin filtrar información interna.
Ejercicios
- Segunda herramienta:
listar_proyectos. Añade aherramientas.pyuna herramienta sin argumentos que devuelva la lista de proyectos del usuario (nombre y estado), y amplíaejecutar_herramienta. Prueba con "¿qué proyectos tengo ahora mismo?" y con "¿cómo va Atlas?" para comprobar que el modelo elige la herramienta correcta en cada caso. - El error como conversación. Con el código de la lección, haz la pregunta "¿cuántas tareas tiene mi proyecto Zafiro?" y observa la respuesta. Después, empobrece a propósito el mensaje de
ProyectoNoEncontrado(déjalo en"error") y repite. Compara ambas respuestas finales y escribe en una frase la conclusión. - Extracción estructurada con herramienta forzada. Define una herramienta
extraer_consultacuyoinput_schematengacategoria(enum con las 6 categorías deprompt_clasificacion()de 02-02) yproyecto(string opcional). Llámala contool_choice: {"type": "tool", "name": "extraer_consulta"}sobre la pregunta "no me deja facturar las horas del proyecto Boreal" y examinabloque.input. ¿Qué pieza de 02-03 acabas de sustituir?
Soluciones
- Definición y despacho:
# En HERRAMIENTAS, añadir:
{
"name": "listar_proyectos",
"description": (
"Lista todos los proyectos del usuario en Nubelia con su nombre y estado. "
"Úsala cuando el usuario pregunte qué proyectos tiene o pida una vista general."
),
"input_schema": {"type": "object", "properties": {}}, # sin argumentos
}
# En api_nubelia.py:
def listar_proyectos() -> list[dict]:
return [{"nombre": p["nombre"], "estado": p["estado"]} for p in _PROYECTOS.values()]
# En ejecutar_herramienta:
if nombre == "listar_proyectos":
return api_nubelia.listar_proyectos()Con "¿qué proyectos tengo?" el modelo debe elegir listar_proyectos; con "¿cómo va Atlas?", consultar_proyecto. Si se confunde, la palanca es la description (¡no el código!).
-
Con el error rico, el modelo responde con alternativas concretas ("tus proyectos son Atlas, Boreal y Cierzo, ¿te referías a alguno?"). Con
"error"a secas, el modelo solo puede decir que algo falló, o peor, especular sobre el motivo. Conclusión en una frase: la calidad de la respuesta final ante un fallo está limitada por la calidad del mensaje de error que devuelve tu herramienta — los errores de las herramientas son parte del prompt. -
Esquema y llamada:
HERRAMIENTA_EXTRAER = {
"name": "extraer_consulta",
"description": "Extrae la categoría y el proyecto mencionado de una consulta de usuario.",
"input_schema": {
"type": "object",
"properties": {
"categoria": {"type": "string",
"enum": ["facturacion", "permisos", "integraciones",
"api", "incidencias", "otros"]},
"proyecto": {"type": "string",
"description": "Nombre del proyecto mencionado, si lo hay."},
},
"required": ["categoria"],
},
}
respuesta = llamar_con_reintentos(
model=MODELO, max_tokens=256,
tools=[HERRAMIENTA_EXTRAER],
tool_choice={"type": "tool", "name": "extraer_consulta"},
messages=[{"role": "user", "content": "no me deja facturar las horas del proyecto Boreal"}],
)
bloque = next(b for b in respuesta.content if b.type == "tool_use")
print(bloque.input) # {'categoria': 'facturacion', 'proyecto': 'Boreal'}Acabas de sustituir el tándem "pedir JSON en el prompt + limpiar_json": con la herramienta forzada no hay prosa que limpiar ni vallas de código que quitar. validar_contrato (la validación semántica) seguiría teniendo trabajo — el enum garantiza que categoria es una de las seis, pero nadie garantiza que sea la correcta.
Conclusión
DocuBot acaba de hacer algo que ningún RAG podía: responder con un dato que no estaba escrito en ningún sitio cuando arrancó la conversación. El mecanismo, una vez desmontado el nombre, es honesto y simple — el modelo decide qué función llamar y con qué argumentos, tu código ejecuta y le devuelve el resultado, el modelo redacta —, y sus piezas son viejas conocidas con ropa nueva: la description es prompt engineering (módulo 2), el input_schema es el contrato JSON de 02-03 con garantías sintácticas de fábrica, los errores como tool_result con is_error son la degradación elegante de 03-02 contada al modelo, y tool_choice forzado nos regala de paso la extracción estructurada más robusta que conocemos. Pero nuestra responder_con_herramienta() tiene una limitación estructural que quizá ya te haya hecho ruido: ejecuta un paso de herramienta y termina. Si el usuario pregunta "¿mi proyecto Atlas supera el límite de tareas de mi plan?", hacen falta dos consultas — la documentación para el límite del plan, la API para las tareas de Atlas — y una comparación entre ambas. Eso ya no es un flujo de dos llamadas: es un bucle en el que el modelo razona, actúa, observa el resultado y decide si necesita seguir. Ese bucle tiene nombre — agente — y es exactamente lo que construimos en la siguiente lección, donde además el RAG del módulo 4 sufrirá una transformación elegante: de pipeline a herramienta.
Curso de IA Generativa y LLMs para Desarrolladores
Módulo 1: Fundamentos de la IA generativa
- Qué es la IA generativa y por qué importa a los desarrolladores
- Cómo funciona un LLM: tokens, embeddings y atención
- Panorama de modelos y proveedores
- Limitaciones y riesgos: alucinaciones, contexto y costes
Módulo 2: Prompt engineering
- Anatomía de un prompt: roles, instrucciones y contexto
- Técnicas de prompting: few-shot, cadena de razonamiento y plantillas
- Salidas estructuradas: JSON y control de formato
- Iteración y evaluación de prompts
Módulo 3: Integración de LLMs en aplicaciones
- Primera integración con la API de un LLM
- Streaming, errores y reintentos
- Conversaciones y gestión del contexto
- Costes, latencia y caching
Módulo 4: RAG - Generación aumentada por recuperación
- Embeddings y búsqueda semántica
- Bases de datos vectoriales
- Ingesta y chunking de documentos
- Construcción de un pipeline RAG completo
- Evaluación de sistemas RAG
Módulo 5: Function calling y agentes
- Function calling: conectar el LLM con tu código
- De LLM a agente: el bucle de razonamiento y acción
- Frameworks de orquestación
