Introducción
Un modelo de IA puede responder con texto, pero a veces una aplicación necesita información estructurada antes de poder realizar un trabajo útil. Las llamadas a herramientas permiten que la aplicación describa una operación, como consultar un artículo del catálogo, y que el modelo proponga el nombre de una herramienta y sus argumentos. El modelo no recibe permiso para ejecutar código arbitrario. Produce datos que su Worker debe tratar como entradas no confiables.
En este laboratorio creará POST /catalog-help. Un modelo Llama alojado en Cloudflare recibe una pregunta breve, como «¿El SKU KB-101 está disponible?», y puede proponer la herramienta de solo lectura lookup_catalog_item. Su Worker acepta exactamente una herramienta conocida, valida un objeto de argumentos exacto { sku } y solo después lee un catálogo sintético pequeño. Las herramientas desconocidas, los campos faltantes o adicionales, los SKU con formato incorrecto y las llamadas múltiples nunca llegan al ejecutor.
Utilizará llamadas de funciones tradicionales para que el límite de seguridad sea visible: la inferencia propone, la validación decide y el código de la aplicación ejecuta. El resultado devuelto está limitado a unos pocos campos públicos de los datos de prueba. Nada de este ejercicio concede acceso de escritura, llama a un servicio externo ni permite que el modelo elija código ejecutable.
Este es el quinto laboratorio del curso. Si accedió directamente, complete primero Conectar LabEx a su cuenta de Cloudflare para aprender a usar el terminal de la VM, autorizar Wrangler, confirmar su cuenta de aprendizaje y configurar su ID de cuenta.
El modelo seleccionado, @cf/meta/llama-3.3-70b-instruct-fp8-fast, admite llamadas de funciones y está disponible mediante la asignación estándar de Workers AI. Actualmente, Workers Free incluye 10.000 Neurons al día. Este laboratorio envía únicamente una solicitud breve en vivo de forma local y otra después del despliegue, por lo que no necesita Workers Paid mientras quede asignación gratuita. La inferencia local sigue llegando a Cloudflare y consume uso de la cuenta; deténgase en lugar de reintentar repetidamente si el modelo o la asignación no están disponibles.
La configuración instala Node.js 22.22.0 y Wrangler 4.132.0 local del proyecto en /home/labex/project/tool-call-guard. También proporciona datos de prueba deterministas del modelo y comprobaciones independientes. La configuración no autoriza Wrangler, crea el código fuente del Worker, invoca un modelo, realiza el despliegue ni crea recursos en la nube.
Autorizar la VM y configurar el Worker de llamadas a herramientas
En este paso autorizará esta VM nueva y configurará un Worker desechable. Es posible que su navegador ya tenga una sesión iniciada en Cloudflare Dashboard, pero Wrangler dentro de una VM nueva necesita su propia autorización limitada.
Acceda al proyecto preparado y confirme la versión fijada de la CLI:
cd /home/labex/project/tool-call-guard
npx wrangler --version
Debe aparecer 4.132.0. Solicite únicamente los permisos necesarios para un Worker conectado a IA. Wrangler 4.132.0 también comprueba las dependencias de KV durante la eliminación, por lo que la ruta de limpieza necesita el permiso de KV aunque este laboratorio no cree datos de KV.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write ai:write
Abra el enlace mostrado, introduzca el código actual, revise la cuenta y los permisos y autorice su cuenta de aprendizaje. Nunca envíe el código, la contraseña ni el token a otra persona. Después, revise los datos estructurados de identidad:
npx wrangler whoami --json
Confirme loggedIn: true y genere un nombre único para que la limpieza pueda dirigirse únicamente al Worker de este laboratorio:
RUN="labex-c07-a05-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
El siguiente documento here escribe una configuración JSON normal. Sustituya YOUR_ACCOUNT_ID por el ID real de la cuenta de aprendizaje prevista:
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.js",
"compatibility_date": "2026-09-16",
"workers_dev": true,
"preview_urls": false,
"observability": { "enabled": true, "head_sampling_rate": 1 },
"ai": { "binding": "AI", "remote": true }
}
JSON
El binding AI proporciona al código un identificador seguro env.AI sin incluir una clave de API del modelo en el código fuente. remote: true significa que el desarrollo local sigue llamando al modelo asociado a la cuenta en lugar de simular la inferencia sin conexión.
Comprender el límite de la herramienta
En este paso conectará el binding de la plataforma con el límite que debe aplicar la aplicación.
Genere los tipos de entorno a partir de wrangler.jsonc y revise la interfaz generada:
npx wrangler types
grep -A4 'interface __BaseEnv_Env' worker-configuration.d.ts
Busque AI: Ai. Una descripción de herramienta son datos estructurados que se envían al modelo: un nombre, un propósito en lenguaje sencillo y un esquema para los posibles argumentos. Ayuda al modelo a proponer una llamada, pero no es una autorización ni código ejecutable.
Este laboratorio permite una herramienta de solo lectura, lookup_catalog_item, con un argumento como { "sku": "KB-101" }. Después de la inferencia, la aplicación exige exactamente una llamada propuesta y el nombre exacto permitido. A continuación, exige que arguments sea un objeto que contenga únicamente sku, comprueba el formato breve de SKU público del laboratorio y pasa el valor validado solo a la función fija de solo lectura de la aplicación.
Revise los datos de prueba de rechazo proporcionados:
grep -nE 'unknown tools|missing, extra|zero or multiple' test/worker.test.mjs
Son respuestas falsas deliberadas del modelo. Demuestran el límite de seguridad sin consumir Neurons ni depender de que un modelo en vivo produzca una llamada con formato incorrecto.
Crear la herramienta de catálogo validada
En este paso describirá la herramienta para el modelo, validará la propuesta del modelo y ejecutará únicamente la función de catálogo de solo lectura de la aplicación.
Cree el punto de entrada del Worker:
cat > src/index.js <<'JS'
const MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
const TOOL_NAME = "lookup_catalog_item";
const MAX_QUESTION = 240;
const SKU_PATTERN = /^[A-Z]{2}-[0-9]{3}$/;
const CATALOG = [
{ sku: "KB-101", name: "Compact Keyboard", priceUsd: 49, inStock: true },
{ sku: "MS-205", name: "Wireless Mouse", priceUsd: 29, inStock: false }
];
const TOOLS = [{
name: TOOL_NAME,
description: "Read one public catalog item by the exact SKU stated in the user's question.",
parameters: {
type: "object",
properties: { sku: { type: "string", description: "An exact catalog SKU such as KB-101" } },
required: ["sku"]
}
}];
function json(data, status = 200) { return Response.json(data, { status }); }
async function readQuestion(request) {
if (!(request.headers.get("content-type") || "").toLowerCase().includes("application/json")) {
return { error: json({ error: "json_required" }, 415) };
}
let body;
try { body = await request.json(); } catch { return { error: json({ error: "invalid_json" }, 400) }; }
const question = typeof body?.question === "string" ? body.question.trim() : "";
if (!question) return { error: json({ error: "invalid_question" }, 400) };
if (question.length > MAX_QUESTION) return { error: json({ error: "question_too_large" }, 413) };
return { question };
}
export function validateToolSelection(toolCalls) {
if (!Array.isArray(toolCalls) || toolCalls.length !== 1) throw new Error("exactly one tool call is required");
const call = toolCalls[0];
if (!call || call.name !== TOOL_NAME) throw new Error("unknown tool");
const args = call.arguments;
if (!args || typeof args !== "object" || Array.isArray(args)) throw new Error("arguments must be an object");
if (Object.keys(args).length !== 1 || !Object.hasOwn(args, "sku")) throw new Error("unexpected arguments");
if (typeof args.sku !== "string" || !SKU_PATTERN.test(args.sku)) throw new Error("invalid sku");
return { name: TOOL_NAME, arguments: { sku: args.sku } };
}
export function executeCatalogTool(argumentsValue) {
const item = CATALOG.find((candidate) => candidate.sku === argumentsValue.sku);
return item ? { ...item, found: true } : { sku: argumentsValue.sku, found: false };
}
export async function handleCatalogHelp(request, env, execute = executeCatalogTool) {
const parsed = await readQuestion(request);
if (parsed.error) return parsed.error;
const requestId = crypto.randomUUID();
let inference;
try {
inference = await env.AI.run(MODEL, {
messages: [
{ role: "system", content: "Use exactly one provided read-only tool. Copy only the exact SKU from the user. Do not answer from memory." },
{ role: "user", content: parsed.question }
],
tools: TOOLS,
max_tokens: 128,
temperature: 0
});
} catch {
console.error(JSON.stringify({ event: "tool_inference_failed", requestId, model: MODEL }));
return json({ error: "model_unavailable", requestId }, 502);
}
let selected;
try { selected = validateToolSelection(inference?.tool_calls); }
catch {
console.error(JSON.stringify({ event: "tool_call_rejected", requestId, model: MODEL }));
return json({ error: "invalid_tool_call", requestId }, 502);
}
const result = execute(selected.arguments);
console.log(JSON.stringify({ event: "tool_call_executed", requestId, model: MODEL, tool: selected.name, found: result.found }));
return json({ model: MODEL, tool: selected.name, arguments: selected.arguments, result, requestId });
}
export default { async fetch(request, env) {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/health") return json({ status: "ok" });
if (request.method === "POST" && url.pathname === "/catalog-help") return handleCatalogHelp(request, env);
return json({ error: "not_found" }, 404);
} };
JS
Observe el orden: env.AI.run() devuelve datos, validateToolSelection() los reduce a una única estructura permitida y solo después se ejecuta executeCatalogTool(). El modelo nunca proporciona JavaScript, elige una URL ni obtiene acceso a una operación de escritura. Los registros guardan metadatos del ciclo de vida, pero omiten la pregunta del usuario y el resultado del catálogo.
Ejecute las cinco pruebas deterministas y después pida a Wrangler que cree el paquete sin realizar el despliegue:
node --test test/worker.test.mjs
npx wrangler deploy --dry-run
Las pruebas deben mostrar cinco resultados correctos. La ejecución de prueba debe mostrar env.AI como binding de Workers AI. En conjunto, esto demuestra que el código de validación y la configuración del Worker son compatibles antes de que una llamada a un modelo en vivo consuma uso.
Probar una selección de herramienta en vivo
En este paso ejecutará el Worker localmente mientras su binding de IA realiza una inferencia remota real. Solo la consulta del catálogo se ejecuta de forma local; el modelo sigue ejecutándose en Cloudflare.
Inicie Wrangler en segundo plano y espere a que responda la ruta de estado que no usa IA. & crea un trabajo en segundo plano, $! es su ID de proceso y el bucle limitado deja de esperar en cuanto /health responde correctamente:
npx wrangler dev --port 8787 > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
if curl --silent --fail http://127.0.0.1:8787/health; then break; fi
sleep 1
done
Envíe una pregunta breve que contenga un SKU sintético exacto:
curl --silent --show-error http://127.0.0.1:8787/catalog-help \
--header 'Content-Type: application/json' \
--data '{"question":"Is SKU KB-101 in stock and what does it cost?"}'
Espere el modelo Llama exacto, tool: "lookup_catalog_item", argumentos que contengan únicamente KB-101 y los campos limitados del dato de prueba Compact Keyboard. No se evalúa el texto generado, porque la aplicación consume la propuesta estructurada de la herramienta en lugar de texto libre.
Rechace una pregunta vacía antes de realizar la inferencia:
curl --silent --show-error --write-out '\nHTTP %{http_code}\n' http://127.0.0.1:8787/catalog-help \
--header 'Content-Type: application/json' --data '{"question":""}'
Espere {"error":"invalid_question"} y HTTP 400. Esto demuestra que la validación normal de la solicitud se realiza antes de utilizar el modelo.
Desplegar y revisar las pruebas de la herramienta
En este paso desplegará el mismo endpoint y relacionará su comportamiento en ejecución con la información visible de Cloudflare.
Detenga únicamente el proceso de desarrollo guardado, espere a que termine y realice el despliegue:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npx wrangler deploy
Guarde la URL exacta que Wrangler muestra y envíe una pregunta pública:
WORKER_URL="https://YOUR_WORKER_URL"
curl --silent --show-error "$WORKER_URL/catalog-help" \
--header 'Content-Type: application/json' \
--data '{"question":"Is SKU KB-101 in stock and what does it cost?"}'
Confirme que el resultado público utiliza el modelo exacto y la herramienta permitida, devuelve únicamente el argumento SKU validado y contiene los mismos campos limitados del dato de prueba de solo lectura.
Abra Workers & Pages → su Worker labex-c07-a05-... → Bindings. Un binding es la conexión con nombre que pone un servicio de Cloudflare a disposición del código del Worker. Confirme una conexión de Workers AI con el nombre AI; ese nombre permite que el programa llame a env.AI.run(...).

Después abra Observability. Esta página recopila registros de invocaciones y logs de la aplicación. El ejemplo siguiente muestra cuatro eventos correctos y cero errores después de la solicitud pública y las comprobaciones independientes. Su recuento puede ser distinto porque cada solicitud puede contribuir con un registro de invocación y un evento de aplicación, y la entrega al Dashboard puede retrasarse.

El aviso azul del plan Free en esta página se refiere a la asignación de eventos de Workers Logs, no a la inferencia de IA. En el campo de búsqueda, introduzca tool_call_executed y expanda una fila coincidente. El ejemplo muestra dos coincidencias correctas y los campos limitados deliberadamente al comienzo del evento: lookup_catalog_item, el modelo Llama exacto y un ID de solicitud. El evento completo también contiene event: "tool_call_executed" y found: true, pero no registra la pregunta del usuario, la respuesta sin procesar del modelo ni el registro de catálogo devuelto.

Por último, abra AI → Workers AI y mantenga seleccionada la pestaña Neurons. Un Neuron es la unidad de Cloudflare para la computación de Workers AI. La cuenta de ejemplo utilizó 342.34/10k Neurons ese día; su fila de Llama muestra 341.57, mientras que un ejercicio anterior de embeddings aparece por separado. Son ejemplos compartidos de una cuenta, no un coste garantizado para una sola solicitud. Busque la fila exacta de Llama en su cuenta y confirme que el total de hoy se mantiene dentro de la asignación de 10k de Workers Free.

Las páginas del Dashboard le ayudan a relacionar la configuración, el tráfico y el uso con el resultado de la línea de comandos. No repita la inferencia únicamente para forzar la actualización de un gráfico. La respuesta JSON y la verificación independiente siguen siendo la referencia, porque los gráficos y los logs pueden llegar más tarde.
Eliminar el Worker y cerrar la sesión
En este paso eliminará el endpoint público desechable y después quitará la autorización de esta VM. El uso de Workers AI permanece en el historial de la cuenta; eliminar el Worker no borra el registro de uso.
Elimine exactamente el Worker cuyo nombre aparece en wrangler.jsonc:
npx wrangler delete
Confirme solo cuando Wrangler muestre el nombre único de este laboratorio, labex-c07-a05-.... Espere Successfully deleted y después ejecute la comprobación independiente de ausencia en la nube mientras la autorización siga disponible:
python3 .labex/verify.py deleted
Solo después de que muestre PASS: deleted, cierre la sesión y revise el estado estructurado:
npx wrangler logout
npx wrangler whoami --json
Debe aparecer loggedIn: false. Cerrar una pestaña del navegador o eliminar el código fuente local no demostraría que el Worker público ha desaparecido.
Resumen
Separó la selección del modelo de la autoridad de la aplicación. Workers AI propuso una consulta de catálogo estructurada, su Worker validó el nombre exacto de la herramienta y el objeto de argumentos, y solo después se ejecutó el código fijo de solo lectura. Los datos de prueba deterministas demostraron que las herramientas desconocidas, los argumentos con formato incorrecto y las llamadas múltiples no pueden actuar, mientras que la inferencia en vivo demostró el intercambio real con el modelo. También revisó pruebas con privacidad limitada y eliminó el Worker desechable y la autorización de la VM.



