Lesson 10 · 15 in the route · 3 min read · Level 3
Lab 02 — Tool loop and structured output
Turn a scraped product name into valid JSON every time, calling a local catalogue tool when needed: the tool loop by hand, schemas and validation.
Spanish only for now · Solo en español por ahora
Proyecto: farmacias (Radar de Precios; matcher de 3 capas). Objetivos: D2.T3.b · D8.T1.a/b/c · D6.T3.a/b · D6.T2.a/b · D5.T1.d Tiempo: 4–5 h.
Requerimiento
Dado el nombre comercial de un producto scrapeado (“PANADOL ANTIGRIPAL NF X 100 TAB”), obtener
{principio_activo, concentracion, forma_farmaceutica, presentacion, es_medicamento}como JSON válido siempre, consultando el catálogo local (DIGEMID) cuando haga falta. Si el texto no es un medicamento (un shampoo),es_medicamento=falsey el restonull. Tasa de JSON inválido: 0 %.
Decisiones
- ¿Cómo garantizas JSON válido? (tool como schema con
tool_choiceforzado, o structured outputs si está disponible; + validación Pydantic). - ¿Qué tools necesita el modelo? (
buscar_digemid(texto)de lectura). ¿Qué NO necesita? (nada de escritura). - ¿Few-shot? (3 ejemplos canónicos: medicamento simple, combinación, no-medicamento).
- ¿Qué va en
system? (rol, reglas, ejemplos; cacheable) ¿y enuser? (el nombre en<producto>).
Pasos
1. Tool de búsqueda con buena descripción
buscar_digemid: descripción de 4 frases (qué, cuándo, cuándo no, qué devuelve).input_schemaestricto. Devuelve top-5 candidatos con campos mínimos (poda).- Errores: texto vacío →
is_error: truecon mensaje útil. Excepción de la BD →is_errordistinto.
2. El loop
- Implementa el loop tú mismo (no
tool_runnertodavía):while stop_reason == "tool_use", ejecutar todos lostool_usedel turno, devolver todos lostool_resulten un mensajeuser, límite de 5 iteraciones. - Prueba paralelismo: pide normalizar 3 productos en una sola consulta y observa si emite 3
tool_useen un turno. Luegodisable_parallel_tool_use=Truey compara.
3. Salida estructurada
- Tool
emitir_resultadocon el schema de salida; en el turno final,tool_choice={"type":"tool","name":"emitir_resultado"}. - Validar
block.inputcon un modelo Pydantic. Si falla: un reintento con el error de validación comotool_resultis_error. Si vuelve a fallar: excepción explícita y log. - Alternativa: si la API expone structured outputs para tu modelo, implementar también esa vía y comparar.
4. Después, con el SDK
- Reescribir el loop con
client.beta.messages.tool_runner(o el helper vigente). Anotar qué te ahorra y qué te oculta.
5. tool_choice y is_error
- Probar
auto/any/noneen el mismo prompt y anotar comportamiento. - Test: la BD devuelve error → el modelo debe explicar que no pudo consultar, no inventar un principio activo.
Verificación
- 50 nombres reales del scraper → 0 JSON inválidos; exactitud vs tu matcher de 3 capas anotada.
- Test: “SHAMPOO HEAD & SHOULDERS” →
es_medicamento=false. - Test: tool falla → salida honesta,
is_erroren la traza. - Test:
max_tokensbajo → detectado (no es JSON válido “por casualidad”). - Descripción de la tool evaluada: 20 consultas, ¿elige llamar la tool cuando debe y no cuando no?
Qué aprendiste
- ¿En qué rol va
tool_resulty qué lo enlaza? - ¿Por qué validar aunque uses
tool_choiceforzado? - Tres cosas que debe decir una descripción de tool.
- ¿Qué es
is_errory qué no es? - ¿Para qué sirven los few-shot aquí y qué riesgo tienen?
- ¿Cuándo usarías
tool_choice: any?