El camino no lo fija el código: lo traza el modelo.
Un agente que lee una oferta de empleo, la contrasta con mi CV y redacta un borrador de candidatura — orquestando tres herramientas en un bucle de function calling escrito a mano sobre el SDK de OpenAI, sin LangGraph. Desplegado en Cloud Run, protegido por token. Cuarto artefacto de la ruta Data Science → AI Engineering.
Del RAG de un paso al agente de un bucle
El RAG del Artefacto 2 es de un solo salto: pregunta → recupera → responde. Un agente es otra cosa: tiene varias herramientas y un bucle que decide cuál usar, en qué orden y cuándo ha terminado. El camino no está fijado en el código — lo traza el modelo, leyendo el resultado de cada paso.
Ese orden no está escrito en ningún if: el modelo lo elige solo, consulta a consulta.
Tú pegas la oferta; el bucle piensa, actúa, observa el resultado y repite hasta entregar la respuesta.
El bucle, a mano — no una caja opaca
Podría haber usado LangGraph. Elegí escribir el agent loop yo mismo sobre el SDK de OpenAI: esquemas de herramientas en JSON Schema, despachador nombre→función y guardarraíles. El motivo es de criterio, no de nostalgia — quiero entender la mecánica que los frameworks abstraen, y el bucle entero cabe en una pantalla.
for turno in range(1, max_turnos + 1): # guardarraíl: nunca infinito
resp = cliente.chat.completions.create(
model=MODELO, messages=mensajes, tools=TOOLS) # PIENSA
msg = resp.choices[0].message
mensajes.append(msg)
if not msg.tool_calls: # ¿no pide herramientas? → terminó
return msg.content
for tc in msg.tool_calls: # ACTÚA + OBSERVA
mensajes.append(ejecutar_herramienta(tc))
La traza, en vivo
Así se ve una consulta real desde dentro. Cada turno son las tres fases del bucle: piensa (el modelo lee el hilo y decide), actúa (llama una herramienta) y observa (el resultado vuelve al hilo). Cuando ya no pide nada, el bucle termina.
» oferta pegada: «Data Scientist · Madrid · Python, SQL, GCP, Inglés B2…»
turno 1 · PIENSA el modelo lee el hilo y el menú de herramientas
ACTÚA analizar_oferta(texto) # temperatura 0
OBSERVA → requisitos extraídos: imprescindibles + deseables
turno 2 · PIENSA con los requisitos ya en el hilo, toca contrastar
ACTÚA match_cv(requisitos) # temperatura 0
OBSERVA → encaje "alto" · Inglés C1 ≥ B2 ✓
turno 3 · PIENSA tiene el encaje: falta la carta
ACTÚA redactar_borrador(encaje) # temperatura 0,7
OBSERVA → borrador listo, reglas de estilo horneadas
turno 4 · PIENSA nada más que pedir → msg.tool_calls vacío
FIN → devuelve la carta + la traza de pasos # exit del bucle
Nadie fijó ese orden. No hay un if que diga «primero analiza»: el modelo lee el
menú (JSON Schema), decide, observa y repite. Por eso el agente devuelve la traza de pasos
y no solo el texto final — para poder auditar qué decidió y por qué.
La interfaz cazó un error de criterio
Al probar el CLI con una oferta nueva (no la de ejemplo), el agente marcó «Inglés B2» como requisito que faltaba… teniendo el CV un C1. Comparaba los niveles de forma literal, sin entender que el marco europeo está ordenado (A1 < A2 < B1 < B2 < C1 < C2) y que un nivel superior cumple uno inferior.
# IDIOMAS — marco CEFR, niveles ORDENADOS A1<A2<B1<B2<C1<C2.
# Un nivel IGUAL O SUPERIOR cumple el requisito
# (un CV con C1 CUMPLE una oferta que pide B2). nativo/bilingüe = C2.
# antes: encaje "medio", "Inglés B2" → BLOQUEANTE ✗
# después: encaje "alto", "Inglés B2" → cumplido ✓
Un agente que dice «no cumples» cuando sí cumples te haría descartar buenas ofertas. Se detectó, se
corrigió con una regla en el prompt y se verificó re-ejecutando el mismo caso — antes de desplegar.
Y el fallo no se quedó en anécdota: hoy es el caso of_bug_cefr_b2 de la báscula del
Artefacto 5 — el bug real convertido en prueba de regresión
permanente.
Un motor, varias interfaces
analizar_oferta, match_cv, redactar_borrador); extracción y juicio a temperatura 0, redacción a 0,7. El CV es conocimiento, cargado por context stuffing: cabe entero, sin RAG.
401 y el agente ni se ejecuta (fail-closed). Protege la API key.
.env no.
Los cuadernos del artefacto
Cada concepto nuevo del recorrido se escribe como un cuaderno propio, no como un comentario en el código. Estos son cuatro de los seis de este artefacto; la colección de la ruta llega a dieciocho.
.env, jamás.
Los criterios, cumplidos
Demo protegida por token. La página carga para cualquiera (prueba de que es real), pero ejecutar el agente requiere un token de acceso: cada consulta gasta llamadas al LLM y el portero evita el uso indebido de la API key. Es una decisión de coste y seguridad, no un adorno.
Cualquiera importa un framework de agentes. Lo que distingue es haber escrito el bucle: saber qué hace por debajo el tool use, dónde poner los guardarraíles y cómo protegerlo en producción. No usar un agente — entenderlo.