SEXTANTEcursos técnicos de IA
métodoromper-y-arreglar
presupuestoatención
Entrar
N3 · Tools y just-in-time/L2

Curar el toolset

Objetivo de maestría

consolidarás y reescribirás el catálogo de Magallanes según los principios de diseño de herramientas —consolidar, describir, namespacing, respuestas concisas— y validarás cada decisión con un eval de selección. De 40 tools a ≤12 sin perder cobertura, con la accuracy igual o mejor que antes.


2.1El problema

Activaste el flag del lab. Magallanes amaneció con 40 tools donde antes tenía 3. La factura por turno se multiplicó y, en el primer run largo, llamó a leer_resumen cuando tocaba leer. Lo mediste en L1: el catálogo inflado es la causa de la confusion que plantamos en N0.

La tentación obvia es atajar: borrar 28 tools a ciegas y confiar. Quitas las que "parecen redundantes", recompilas y a otra cosa.

Dos semanas después nadie recuerda por qué Magallanes ya no sabe filtrar por fecha. Y lo peor: la accuracy de selección no mejoró. Las 12 supervivientes siguen mal descritas y solapadas. Podaste el árbol y la confusión sigue ahí.

Aquí está la distinción que vertebra esta lección. Curar no es podar. Podar es quitar tools. Curar es rediseñar el catálogo con un eval delante: consolidar operaciones, reescribir cada descripción y verificar —con números— que la selección mejora. Sin esa verificación, optimizas la estética del catálogo, no la decisión que de verdad falla.

En esta lección coges la avería de L1 y la curas. Sales con un catálogo de ≤12 tools, cada una justificada por un principio, y una tabla de accuracy antes/después.


2.2Qué vas a poder hacer

Al terminar serás capaz de:

  • Consolidar familias de tools fragmentadas en operaciones únicas con parámetros, justificando cada fusión.
  • Reescribir una tool definition según los principios de Anthropic: qué hace, cuándo usarla y cuándo NO.
  • Aplicar namespacing, semántica sobre UUIDs y respuestas concisas, decidiendo con evals cuando el principio admite variantes.
  • Validar cada decisión con un eval de selección antes/después, demostrando que la cobertura funcional sobrevive.

Necesitas saber antes:

  • De L1 (cada-tool-paga-renta): qué partida del presupuesto pagan las tool definitions, su coste por unidad (rango) y la evidencia de que más tools degradan la selección. Lo recuperamos en 2.3.
  • De N0·L4 (el-context-sweep): cómo monta el harness un eval con call_api. Reutilizarás esa firma para el eval de selección.
  • El esqueleto congelado de Magallanes: sus 3 tools buscar, leer, escribir_seccion, y las 4 familias fragmentadas que añade el flag lab_n3.

2.3Recupera

Antes de seguir, responde de memoria. Esto reactiva L1 y engancha lo nuevo.

  1. En "Less is More", Llama 3.1 8b cuantizado acertó con 19 tools y falló con 46. Las 46 cabían en su ventana de 16k. ¿Qué demuestra eso sobre la causa del fallo?
  2. ¿Qué partida del presupuesto pagan las tool definitions, y cuánto cuesta una (rango orientativo)?
  3. En la familia de "getters atómicos" del flag (obtener_titulo, obtener_autor, obtener_fuente…), ¿qué operación real está fragmentada en piezas?
Comprueba tu respuesta
  1. Que el fallo no es de capacidad de ventana: las 46 tools cabían. Es de selección. Con 46 candidatas "it fails to select the correct one"; con 19 acierta. Más tools degradan la elección aunque quepan. · fuente: corpus A.3 / D.2 (Less is More, arXiv:2411.15399).
  2. El coste fijo del contexto estático: las definitions entran antes del primer turno. Cada una cuesta ~500–1.500 tokens (rango orientativo, confianza media). · fuente: corpus D.4.
  3. Recuperar el contexto de un documento. Tres getters atómicos que devuelven título, autor y fuente por separado son una sola operación troceada — el espejo exacto del get_customer_context del post de Anthropic, que reemplaza 3 getters. · fuente: corpus D.1.

2.4El concepto

El principio raíz: pocas tools deliberadas

Empezamos por la regla de la que cuelga todo lo demás. Anthropic la formula así en "Writing effective tools for agents" (11 sep 2025):

"build a few thoughtful tools targeting specific high-impact workflows… scaling up from there."

Léelo dos veces. No dice "muchas tools por si acaso". Dice pocas, deliberadas, sobre flujos de alto impacto — y escalar desde ahí solo si hace falta. El flag lab_n3 hizo justo lo contrario: generó 37 variantes redundantes sin un flujo que las justifique. Curar es devolver el catálogo a esa frase.

De este principio salen cuatro decisiones de diseño. Las veremos una a una, cada una con su evidencia.

Decisión 1 — consolidar operaciones

Una tool consolidada es una herramienta que cubre con parámetros lo que antes hacían varias herramientas separadas. En vez de exponer cada paso atómico, expones la operación completa.

El post lo ilustra con dos ejemplos verbatim. Primero: un schedule_event que sustituye a list_users + list_events + create_event. El agente ya no encadena tres llamadas para agendar; pide una. Segundo —y este es el espejo de tu avería—: un get_customer_context que sustituye a tres getters separados.

La familia de getters atómicos del flag (obtener_titulo, obtener_autor, obtener_fuente, …) es ese antipatrón. Cada getter devuelve un campo. Para entender un documento, Magallanes llama a cinco. Consolidar significa que leer ya devuelve el contexto que esas piezas troceaban.

La analogía: es la diferencia entre pedir "el dossier del cliente" y pedir su nombre, luego su teléfono, luego su factura en tres peticiones. Dónde falla: el dossier humano es fijo; una tool consolidada expone parámetros para pedir solo lo que necesitas. Consolidar no es "darlo todo siempre", es una operación con opciones.

Decisión 2 — describir cada tool con precisión

La descripción de una tool es lo que el modelo lee para elegirla o descartarla. El flag sabotea esto a propósito: descripciones vagas y solapadas. Si dos tools dicen "busca documentos", el modelo no tiene cómo distinguirlas. Ahí nace la confusion.

Una descripción que cura dice tres cosas: qué hace la tool, cuándo usarla y cuándo NO. La cláusula "cuándo NO" es la que más gente omite. Es la que traza la frontera con la tool vecina y le da al modelo el criterio para descartar.

Es común creer que describir lo que la tool hace ya alcanza. No alcanza. Si buscar y leer no dicen explícitamente cuándo NO se usan, el modelo las confunde en los casos ambiguos — donde "Less is More" mostró que falla.

Decisión 3 — namespacing, semántica y respuestas concisas

Tres ajustes que el post respalda con datos, y que comparten una lección: se deciden midiendo, no por gusto.

Namespacing. Agrupar tools por prefijo o sufijo (buscar_doc, buscar_web) le da al modelo señales de agrupación para discriminar candidatas similares. El post advierte que prefijo-vs-sufijo tiene "non-trivial effects on our tool-use evaluations" y varía por modelo. No hay respuesta universal; lo decides con tu eval.

Semántica sobre UUIDs. Sustituir identificadores opacos por lenguaje semántico o índices da precisión "significantly better", según el post. Un doc_id legible (doc-magallanes-ruta) supera a un UUID crudo.

Respuestas concisas. El post reduce un caso de 206 a 72 tokens (~65% menos) sin perder lo accionable. Una respuesta concisa paga menos renta por turno — la cuarta partida del presupuesto de N0.

Sobre el formato de respuesta (XML, JSON, Markdown), el post es explícito: "no one-size-fits-all". Lo testeas.

El método: Prototype → Evaluate → Collaborate

Ninguna de estas decisiones se valida "a ojo". El post propone un ciclo: Prototype (escribe la versión consolidada), Evaluate (mídela contra un held-out set), Collaborate (afínala con el modelo en el loop). El held-out set es un conjunto de casos que reservas para evaluar y que NO usaste para diseñar. Así la accuracy que mides no está contaminada por el ajuste.

Las métricas del post para evaluar tools: accuracy (¿eligió la tool correcta?), runtime, número de tool calls, consumo de tokens y error rates. En esta lección usas la primera: accuracy de selección antes/después.

Normaliza esto, porque es el fallo número uno. Curar "a ojo" optimiza la estética del catálogo, no la selección. Reescribes descripciones más limpias, reduces de 40 a 12, y te sientes productivo. Sin un eval antes/después, no sabes si mejoraste la elección o solo la moviste de sitio. El eval separa curar de redecorar.

Lo que NO es curar

Curar no es reducir el número de tools y ya. Pasar de 40 a 12 no garantiza nada si las 12 siguen vagas y solapadas. La cifra baja; la confusion se queda.

Curar tampoco es fusionar todo en una mega-tool con veinte parámetros. Eso traslada la confusión de "qué tool elijo" a "qué parámetros relleno". El post pide pocas tools deliberadas, no una sola tool imposible.


2.5Míralo funcionar

Vamos a curar la familia de búsqueda del flag (buscar_por_titulo, buscar_por_autor, buscar_por_fecha…) → una buscar consolidada con parámetros. (El lab te da el conteo exacto de variantes al activar el flag.) Y a probar con un eval que la selección mejora.

Es código con tres piezas: la definición curada, su descripción comentada y el eval que la valida. Antes de leerlo línea a línea, lee el bloque entero una vez. Lo difícil no es una línea suelta. Es ver cómo descripción y eval trabajan juntos.

Paso 1 — consolidar: de la familia de búsqueda a una buscar

Las variantes de búsqueda fragmentan una operación: buscar en la biblioteca. Cada una fija un eje (título, autor, fecha, snippets). Una sola buscar con parámetros los cubre todos.

python
1# La tool congelada de Magallanes, consolidada con parámetros.
2# Reemplaza las variantes de búsqueda fragmentadas del flag lab_n3.
3from langchain_core.tools import tool
4
5@tool
6def buscar(
7    query: str,
8    campo: str = "todos",        # "titulo" | "autor" | "fecha" | "snippets" | "todos"
9    top_k: int = 5,
10) -> list[dict]:
11    """Busca documentos en la biblioteca y devuelve sus referencias ligeras.
12
13    Usa esta tool cuando NO sepas aún qué documento leer y necesites
14    localizar candidatos por tema, título, autor o fecha.
15
16    - 'query': lo que buscas, en lenguaje natural.
17    - 'campo': acota la búsqueda a un eje; "todos" busca en todos.
18    - 'top_k': cuántas referencias devolver (sube poco la renta por turno).
19
20    NO uses esta tool si YA tienes el doc_id y quieres su contenido:
21    para eso está 'leer'. NO la uses para escribir: usa 'escribir_seccion'.
22
23    Devuelve: [{"doc_id": "doc-...", "titulo": str, "snippet": str}, ...]
24    con doc_id semánticos (no UUIDs), listos para pasar a 'leer'.
25    """
26    ...   # implementación del lab (índice del corpus del lab)

Mira la descripción línea a línea, porque cada parte hace un trabajo:

  • Línea 1 (qué hace): "Busca documentos… y devuelve sus referencias ligeras". Una frase, sin solapar con leer.
  • "Usa esta tool cuando…": fija el caso de uso positivo — localizar candidatos cuando aún no sabes qué leer.
  • Los parámetros: campo absorbe lo que antes eran cuatro tools distintas; top_k te deja controlar la renta de la respuesta.
  • "NO uses esta tool si…": la cláusula de frontera. Le dice al modelo cuándo descartar buscar a favor de leer o escribir_seccion. Esto es lo que mata la confusion.
  • El return: doc_id semánticos, no UUIDs — el principio de "semántica sobre UUIDs" aplicado a la salida.

Paso 2 — el eval de selección

Una descripción más limpia no prueba nada por sí sola. Lo pruebas con un eval. Reutilizas la firma call_api del harness de N0·L4: promptfoo te pasa una query, tú devuelves qué tool eligió Magallanes.

python
1# provider_seleccion.py — mide QUÉ tool elige Magallanes ante cada query.
2# Firma EXACTA de promptfoo (corpus F.8): call_api(prompt, options, context) -> {"output": ...}
3from magallanes import construir_grafo
4
5def call_api(prompt: str, options: dict, context: dict) -> dict:
6    cfg = context.get("vars", {})
7    catalogo = cfg.get("catalogo", "inflado")   # "inflado" (40) o "curado" (≤12)
8
9    # El grafo se monta con el catálogo elegido; el resto, congelado.
10    graph = construir_grafo(catalogo=catalogo)
11    result = graph.invoke(
12        {"messages": [{"role": "user", "content": prompt}]},
13        config={"configurable": {"thread_id": f"sel-{catalogo}"}},
14    )
15
16    # La primera tool que el agente decidió invocar (el AIMessage con tool_calls).
17    for msg in result["messages"]:
18        if getattr(msg, "tool_calls", None):
19            return {"output": msg.tool_calls[0]["name"]}   # nombre de la tool elegida
20    return {"output": "NINGUNA"}

Tres cosas de este provider:

  • catalogo es la única variable que cambia entre las dos pasadas: "inflado" (las 40 del flag) vs "curado" (las ≤12 tuyas). Todo lo demás —el grafo, las queries— queda congelado. Es el control que separa medir la curación de medir otra cosa.
  • Devuelve el nombre de la tool elegida, no el informe final. El eval de selección juzga la decisión, no el resultado.
  • tool_calls es el campo del AIMessage que el modelo rellena —vía la respuesta de la API— cuando decide invocar una tool. LangGraph lo lee para decidir el ruteo al ToolNode. Lo viste en el grafo congelado de Magallanes.

Paso 3 — el config: 10 queries con su tool esperada

yaml
1# seleccion.yaml — un caso por query; la tool esperada es el ground truth.
2providers:
3  - id: 'file://provider_seleccion.py'
4
5prompts:
6  - '{{query}}'
7
8tests:
9  # Cada caso: la query y qué tool DEBERÍA elegir Magallanes.
10  - vars: { query: "Busca documentos sobre la ruta de Magallanes", catalogo: curado }
11    assert: [{ type: equals, value: "buscar" }]
12  - vars: { query: "Léeme el documento doc-magallanes-ruta entero", catalogo: curado }
13    assert: [{ type: equals, value: "leer" }]
14  - vars: { query: "Redacta la sección sobre financiación", catalogo: curado }
15    assert: [{ type: equals, value: "escribir_seccion" }]
16  # ... 7 casos más, mezclando ejes de búsqueda y lecturas

El tipo de assert equals comprueba igualdad exacta (nativo de promptfoo); los tipos de F.8 muestran también contains y llm-rubric para evaluaciones más complejas. La accuracy de selección es la fracción de los 10 casos que aciertan.

El resultado: la tabla antes/después

Corres el eval dos veces, cambiando solo catalogo. Estos números son ilustrativos —tu run dará los suyos—; lo que importa es la comparación:

catálogo  | tools | accuracy de selección (10 queries)
----------+-------+------------------------------------
inflado   |  40   | 0.50   (5/10 — confunde buscar_por_titulo con buscar)
curado    |  12   | 0.90   (9/10 — frontera clara entre buscar/leer/escribir)

Lee la comparación, no el dígito absoluto. Con 40 tools, Magallanes acierta la mitad: las variantes solapadas se canibalizan. Con 12 curadas y frontera clara, acierta nueve de diez. Esa diferencia prueba que curaste, no podaste. Si la curada hubiera dado 0.50 también, sabrías que tu rediseño fue cosmético.

Self-explanation

Antes de leer la respuesta, intenta tú: ¿por qué la descripción de buscar dice cuándo NO usar la tool?

Razónalo y comprueba

Porque la confusion nace en los casos ambiguos, donde dos tools parecen aplicables. La cláusula "cuándo NO" le da al modelo el criterio para descartar.

Sin ella, ante "léeme el doc-014", el modelo podría dudar entre buscar (que también ve documentos) y leer. La frontera "NO uses buscar si YA tienes el doc_id" resuelve esa duda explícitamente. Es lo que "Less is More" no tenía: con 46 tools vagas, el modelo "fails to select the correct one". · fuente: corpus A.3 / D.2.

Si pensaste "para documentar la tool", revisa. No es documentación para humanos: es el criterio de descarte que el modelo usa en tiempo de selección.


2.6Hazlo tú

Andamiaje decreciente. Curas la familia de lectura con la receta del worked example, luego escritura y getters por tu cuenta. El objetivo global: catálogo ≤12 tools, cobertura intacta, accuracy ≥ la del catálogo de 40.

Ejercicio 1 — la familia de lectura (con pasos)

El flag fragmentó leer en variantes: leer_resumen, leer_primera_pagina, leer_metadatos, … Cúralas en una sola leer consolidada. Sigue la receta de 2.5:

  1. Consolida: una leer(doc_id, ...) con un parámetro que cubra los modos fragmentados (resumen, página, metadatos). Pista: un parámetro formato o secciones.
  2. Describe: rellena los huecos. Qué hace: ________. Cuándo usarla: ________. Cuándo NO (la frontera con buscar): ________.
  3. Valida: añade 3 casos de lectura al seleccion.yaml y corre el eval con catalogo: curado vs inflado.

Ejercicio 2 — escritura y getters (sin pasos)

Cura la familia de escritura (crear_seccion_vacia, anadir_parrafo, cerrar_seccion, …) y los getters atómicos. Sin receta esta vez. Recuerda: los getters son el espejo del get_customer_context del post — consolídalos en lo que leer ya devuelve.

Cuando termines, cuenta tus tools. Si pasas de 12, pregúntate cuál fragmenta una operación que otra ya cubre.

Elaborative interrogation — antes de correr el eval, predice: ¿la accuracy de selección del catálogo curado será mayor, igual o menor que la del inflado? ¿Por qué?

Comprueba tu predicción

Esperas que sea mayor. Reduces el número de candidatas y, sobre todo, eliminas el solapamiento: las descripciones con frontera "cuándo NO" le dan al modelo cómo descartar.

La evidencia: en "Less is More", pasar de 46 a 19 tools convirtió un fallo en acierto, aun cabiendo las 46. · fuente: corpus A.3 / D.2. Y RAG-MCP: pre-filtrar por descripción subió la accuracy de 13,62% a 43,13% —más de 3×—, con ~49% menos tokens. · fuente: corpus D.2.

Feedback: si predijiste "igual o menor porque pierdo capacidades", revisa la cobertura. La buscar consolidada no perdió la capacidad de filtrar por fecha — la movió a un parámetro. Cobertura intacta, menos confusion. Si tu accuracy bajó, probablemente perdiste un caso de uso: revisa qué eje de búsqueda dejaste fuera.


2.7Comprueba

Sin pistas. Gate de maestría: reescribir dos definiciones defectuosas y justificar cada cambio con el principio que lo respalda.

Un compañero te pasa estas dos tools del catálogo de Magallanes. Las dos violan principios de diseño. Encuéntralos, reescribe y justifica.

python
1# Tool A — descripción vaga y solapada
2@tool
3def buscar_docs(q: str) -> list[dict]:
4    """Busca documentos."""
5    ...
6
7@tool
8def buscar_papers(q: str) -> list[dict]:
9    """Busca papers y documentos en la base."""
10    ...
python
1# Tool B — respuesta enorme con UUIDs
2@tool
3def leer_doc(uuid: str) -> dict:
4    """Lee un documento."""
5    # devuelve, entre otros campos:
6    # {"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7    #  "author_uuid": "9b2e...", "source_uuid": "3c1d...",
8    #  "full_text": "<8.000 caracteres>", "raw_xml_metadata": "<2.000 caracteres>"}
9    ...
  1. Identifica qué principio viola cada tool.
  2. Reescribe ambas.
  3. Justifica cada cambio citando el principio.
Criterio de corrección + feedback

Tool A — descripción vaga y solapada (viola: describir + consolidar).

buscar_docs y buscar_papers hacen lo mismo con descripciones casi idénticas. El modelo no tiene cómo elegir → confusion. Arreglo: consolidarlas en una buscar(query, campo, top_k) con descripción que diga qué hace, cuándo usarla y cuándo NO (la frontera con leer). · principio: consolidar operaciones + describir con precisión (corpus D.1).

Tool B — respuesta enorme con UUIDs (viola: semántica sobre UUIDs + respuestas concisas).

Devuelve UUIDs crudos (f47ac10b-…) y vuelca 10.000 caracteres por llamada. Arreglo: (a) sustituir UUIDs por doc_id semánticos (doc-magallanes-ruta) — precisión "significantly better"; (b) recortar con defaults sensatos: resumen + doc_id para paginar, no el texto entero ni el XML crudo. · principio: semántica sobre UUIDs + respuestas concisas, ejemplo 206→72 tokens (corpus D.1).

Feedback formativo: si viste que A es un problema de selección (dos tools indistinguibles) y B uno de renta por turno (respuesta inflada), dominas la distinción del nivel — las dos partidas que L1 separó. Si solo viste "están mal escritas", repasa 2.4: cada defecto mapea a un principio concreto. Gate: nombra el principio que respalda cada reescritura, no solo reescribas.

Un recordatorio para tu entregable: una reescritura sin eval antes/después no está validada. Por limpia que se vea, mídela.


2.8Conecta

Acabas de hacer la mitad del checkpoint C3, y no es retórica: las dimensiones 1 y 2 de la rúbrica C3 son exactamente esto.

La dimensión 1 —curación validada— pide consolidación justificada y accuracy de selección antes/después. Es tu tabla de 2.5. La dimensión 2 —calidad de definición— pide descripciones según los principios: consolidar, semántica sobre UUIDs, respuestas concisas. Es cada definición que reescribiste. No vas a improvisar el checkpoint: ya tienes el método y la evidencia.

Pero ojo con lo que NO resolviste todavía. Sacaste 28 tools del catálogo. Lo que sacaste no desaparece — filtrar por fecha sigue siendo legítimo, solo que ahora vive en un parámetro de buscar. Y queda algo que no tocaste: el corpus precargado del system prompt, el índice entero de la biblioteca que se paga en cada turno.

Ese índice no se cura podándolo. Se sirve bajo demanda. En L3 (just-in-time-y-progressive-disclosure) aprendes a sustituir conocimiento precargado por referencias ligeras y carga en tiempo de ejecución — la otra mitad del adelgazamiento.

Cierra el arco que abrimos en 2.1: tenías la tentación de podar a ciegas. Ahora tienes un catálogo de ≤12 tools, cada una con su principio, y un número que prueba que la selección mejoró. La diferencia entre podar y curar es esa tabla.


2.9Reflexiona

Tómate un minuto. Responder esto por escrito consolida lo aprendido mejor que releer.

  • ¿Qué aprendiste? Resume en una frase por qué un eval de selección antes/después es lo que separa curar de podar.
  • ¿Qué sigue sin estar claro? ¿Tienes claro por qué la cláusula "cuándo NO" de una descripción reduce la confusion? Si no, vuelve a 2.4, decisión 2, y a la self-explanation de 2.5.
  • ¿Qué harías distinto? La próxima vez que alguien proponga "borremos la mitad de las tools", ¿qué pregunta le harías antes de aceptar?

Esto requiere práctica. La intuición de "qué operación está fragmentada de verdad" llega curando catálogos reales, no leyendo sobre ellos. En L5 integrarás esta curación con el JIT de L3 y los límites de results de L4 en el entregable de C3.