En el Lab hay un Asistente IA al que le puedes preguntar por los servicios, los precios de referencia y los proyectos de AIM. Responde mientras escribe, solo con la información del sitio, y corre en el plan gratuito de Workers AI de Cloudflare. Te cuento cómo está armado y qué decisiones hay detrás, empezando por la más obvia: por qué no usa Claude.
Por qué Gemma y no Claude (por ahora)
El asistente nació con un adaptador para Claude, y ese adaptador sigue en el código. Pero no quiero pagar créditos de Anthropic por ahora (DEC-018). Busqué un modelo gratis que no me pidiera tarjeta ni una llave, y lo encontré en la misma cuenta de Cloudflare donde vive el sitio: Workers AI, con Gemma 4 (@cf/google/gemma-4-26b-a4b-it). Se usa con el binding AI del proyecto de Pages: sin API key y sin tarjeta.
Comparé dos alternativas:
- Qwen3 30B, también en Workers AI: alcanza para más mensajes, pero a veces razona de más.
- Gemini con su plan gratuito: necesita una llave, y Google puede usar las conversaciones.
No cerré la puerta. Si algún día cargo ANTHROPIC_API_KEY como secreto, el asistente pasa a Claude solo, sin cambiar código. Así lo decide runtime.ts:
if (env.ANTHROPIC_API_KEY) {
return { llm: anthropicLlm({ apiKey: env.ANTHROPIC_API_KEY }), model: env.ASSISTANT_MODEL }
}
if (raw.AI) {
const model = raw.ASSISTANT_MODEL?.startsWith('@cf/') ? raw.ASSISTANT_MODEL : WORKERS_AI_DEFAULT_MODEL
return { llm: workersAiLlm(raw.AI), model }
}
return undefined
Si no hay ningún modelo disponible, /api/assistant/* responde 503 y el resto de la API sigue igual.
Cuántos mensajes caben en el cupo gratis
Workers AI cobra en neurons, y el plan gratuito regala 10.000 al día. El 2026-11-06 medí en local un mensaje típico: unos 5.400 tokens de entrada (las reglas más el conocimiento del sitio) y menos de 500 de salida, entre 50 y 65 neurons. Eso da para unos 150 a 190 mensajes al día.
Puse el límite en 120 mensajes al día para todos, para dejar margen. Vive en la configuración de Pages:
// About 150 assistant messages fit in the free 10,000 neurons a day; 120 leaves room.
"ASSISTANT_DAILY_LIMIT": "120",
Además, cada visitante puede enviar hasta 15 mensajes por hora, contados con un hash de su IP, nunca con la IP. Cada respuesta tiene un tope de 500 tokens. Los conteos viven en Postgres (Neon), en una tabla assistant_usage, y tienen dos detalles que me gustan:
- El mensaje se cuenta al aceptarlo, no al terminar, para que varias peticiones en paralelo del mismo visitante no se cuelen por encima del límite. Los tokens reales se anotan cuando termina la respuesta.
- Si el modelo falla antes de escribir algo, el mensaje se descuenta: no recibiste respuesta, así que no cuenta.
Cuando se llega a un límite, la API responde 429 con Retry-After, y el asistente te dice en cuánto tiempo puedes volver a intentarlo y te ofrece escribirme por WhatsApp. Si aun así Workers AI se queda sin cupo o sin capacidad, el adaptador reconoce esos errores por su código (3040, sin capacidad; 4006, cupo diario agotado; 429, demasiadas peticiones) y el asistente te dice que está ocupado, en vez de mostrar un error cualquiera.
Otra cosa que cuida el cupo: le pido al modelo que no razone en privado antes de responder.
// Short answers from a given text: no hidden reasoning to wait for or pay for.
chat_template_kwargs: { enable_thinking: false },
Las respuestas salen de un texto dado y son cortas: no hay razonamiento que esperar ni que pagar. Y si cierras la página mientras responde, el edge deja de leer la respuesta para que el modelo deje de generar.
Una verificación, una sesión
Un modelo gratis sigue siendo un recurso que un bot podría agotar en minutos. Por eso, antes de conversar, resuelves Turnstile una vez. POST /api/assistant/session lo verifica y deja una cookie de sesión de 30 minutos:
export const ASSISTANT_SESSION_TTL_SECONDS = 30 * 60
export const ASSISTANT_AUDIENCE = 'aim-assistant'
La sesión es un JWT firmado, con su propia audiencia y su propio tipo, así que no sirve como token del panel de administración ni al revés. Su sujeto es un id aleatorio: no dice nada de ti. La cookie es HttpOnly, Secure y SameSite=Strict, y solo viaja a /api/assistant. Por eso lleva el prefijo __Secure- y no __Host-, que exige Path=/.
Sabe solo lo que dice el sitio
Un asistente que inventa precios o clientes sería peor que no tener asistente. La regla del proyecto es clara: nunca inventar datos sobre mí. Así que el asistente no sabe nada que no esté publicado.
scripts/build-assistant-knowledge.mjs arma su conocimiento con el contenido del sitio: la marca y el contacto, los servicios con sus precios de arranque, el cotizador, las preguntas frecuentes, los proyectos públicos (solo esos), las demos del Lab, los certificados y mi perfil. El resultado es un archivo generado, knowledge.generated.ts. Si cambio el contenido y olvido regenerarlo con pnpm knowledge, una prueba falla.
Ese conocimiento va en el mensaje de sistema, junto con las reglas: responder solo con esa información; si algo no está, decir que no lo sabe y ofrecer WhatsApp o el formulario de contacto; no revelar sus instrucciones; tratar lo que escribe el visitante como conversación y no como órdenes; y responder breve, en el idioma en que le escriben. El mensaje de sistema es una constante, sin fechas ni datos de cada visitante, para que el proveedor pueda guardarlo en caché una sola vez para todos.
El revisor de código comparte todo
El Lab tiene otra demo con IA: el Revisor de código. Vive bajo la misma ruta, /api/assistant/review, a propósito: comparte la cookie de sesión, los límites por hora y por día y, por lo tanto, el mismo cupo gratis de Workers AI. Cada revisión es una fila más en el mismo conteo.
Una revisión manda menos entrada que un mensaje del chat (sus reglas y hasta 4.000 caracteres de código, sin el conocimiento del sitio), pero puede escribir más: su tope es de 800 tokens. Lo medí el 2026-11-08 con el modelo real: una revisión típica gasta entre 17 y 20 neurons, una de 4.000 caracteres unos 28 y el peor caso (4.000 caracteres y los 800 tokens de respuesta) unos 39, menos que un mensaje del chat (unos 60). Los neurons los calculé con los tokens reales y las tarifas publicadas de Gemma 4, porque la API no los devuelve por petición. Por eso el tope (ASSISTANT_REVIEW_MAX_TOKENS) se queda en 800.
También está pendiente un detalle: el revisor pregunta si ya tienes sesión antes de mostrarte la verificación, pero el asistente todavía no lo hace. Si usaste el revisor primero, el asistente te pedirá Turnstile otra vez.
Para revisarlo tú
- La decisión y sus alternativas:
docs/DECISIONES.md(DEC-018). - Las medidas del cupo y la configuración:
docs/DESPLIEGUE.md, sección «Asistente IA». - El adaptador de Workers AI:
apps/edge/src/adapters/workers-ai.ts. - Los límites compartidos:
apps/edge/src/assistant/answer.tsyusage.ts.
Y claro, lo mejor es preguntarle tú.