Junta las cinco partes
Un chatbot que funciona reúne cinco partes en un solo bucle. Guarda estado, lo recorta, sigue un mensaje system, envía su respuesta en flujo y muestra sus fuentes. Importa porque cada parte funciona sola, y los fallos aparecen donde se juntan.
El chatbot de una biblioteca responde tres preguntas seguidas. Debe recordar la primera, mantenerse en su presupuesto y citar una fuente cada vez.
Al final de esta lección
Nota
Puedo reunir estado, recorte, mensaje system, flujo y fuentes en un solo chatbot.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Bucle de chat | los pasos que se repiten con cada mensaje del usuario |
| Turno | un mensaje del usuario y la respuesta que recibe |
| Historial | todos los mensajes del chat hasta ahora |
| Presupuesto | el máximo de tokens que decides enviar |
| Plan B | lo que hace el bot cuando ningún modelo responde |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un turno usa las cinco partes
Cada turno sigue los mismos pasos. Carga el historial. Añade el mensaje del usuario. Recorta lo que vas a enviar. Llama al modelo. Comprueba la respuesta. Guarda el historial.
Escribe esos pasos en una sola función, turn. Entonces el chat es esa función llamada una y otra vez.
Ejemplo resuelto: los pasos de un turno
| Paso | De la lección | Qué ocurre |
|---|---|---|
| Cargar y añadir | Recordar la conversación | El mensaje del usuario se une a la lista de la sesión |
| Recortar | Recortar el historial | Los mensajes viejos quedan fuera de esta llamada |
| Mensaje y fuentes | Mensaje system, Citas | El mensaje system lleva las reglas y las fuentes numeradas |
| Llamar y comprobar | Citas | Se comprueba que la respuesta no tenga marcas sin fuente |
| Flujo y guardar | Respuesta en flujo | Los trozos van a la página y se guarda el historial |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Guardar no es enviar
El historial guarda todos los mensajes. La llamada envía solo lo que cabe en el presupuesto. Por eso recortar nunca cambia el registro. Cambia una llamada.
Así también puedes cambiar el presupuesto más tarde, y una persona sigue pudiendo leer el chat entero.
Si el modelo no responde
A veces ningún modelo responde: la IA está desactivada, se acabó el crédito o cae la red. Un plan B es un mensaje claro que lo dice, para que el chat no se cuelgue ni se rompa.
En este laboratorio, cada Run imprime entonces una muestra grabada, señalada como tal. Un producto real mostraría su propio aviso.
Un error frecuente
Advertencia
Mucha gente cree que un chatbot está terminado cuando la demostración funciona. Una demostración usa preguntas amables. Prueba preguntas difíciles, un chat muy largo y ningún modelo. Cuenta los tokens que cuesta un chat antes de que lo use más gente.
Escribir la ayuda del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir la ayuda del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' const list = [].concat(sample);' ' const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return list[Math.min(turn, list.length - 1)];' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el chatbot, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const fs = require(\"fs\");' 'const { ask } = require(\"./model\");' 'const BUDGET = 150; // tokens para el mensaje system y el historial juntos' 'const SOURCES = [' ' \"Los socios pueden llevarse hasta seis libros durante tres semanas.\",' ' \"Un libro devuelto tarde cuesta diez céntimos al día.\",' ' \"La biblioteca abre a las nueve y cierra a las cinco de la tarde en días laborables.\",' '];' > chatbot6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el chatbot, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function tokens(m) { return Math.ceil(m.content.length / 4) + 4; }' 'function total(list) {' ' return list.reduce(function (sum, m) { return sum + tokens(m); }, 0);' '}' 'function systemMessage() {' ' const numbered = SOURCES.map(function (t, at) { return \"[\" + (at + 1) + \"] \" + t; }).join(\" \");' ' const rule = \"Eres el asistente de la biblioteca. Responde en dos frases como máximo, solo con estas fuentes. Escribe el número de la fuente entre corchetes después de cada dato. Si no lo dicen, responde: No lo sé. \";' ' return { role: \"system\", content: rule + numbered };' '}' >> chatbot6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el chatbot, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function trim(history, system) {' ' const kept = history.slice();' ' while (total([system].concat(kept)) > BUDGET && kept.length > 1) kept.shift();' ' return kept;' '}' 'function badMarkers(reply) {' ' const bad = [];' ' const re = /\[(\d+)\]/g;' ' let m = re.exec(reply);' ' while (m) {' ' if (Number(m[1]) < 1 || Number(m[1]) > SOURCES.length) bad.push(m[1]);' ' m = re.exec(reply);' ' }' ' return bad;' '}' >> chatbot6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el chatbot, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function turn(state, text, sample) {' ' state.history.push({ role: \"user\", content: text });' ' const system = systemMessage();' ' const sent = trim(state.history, system);' ' const held = state.history.length;' ' const reply = await ask([system].concat(sent), sample, 70);' ' state.history.push({ role: \"assistant\", content: reply });' ' fs.writeFileSync(\"chat.json\", JSON.stringify(state, null, 1));' ' console.log(\"Tú: \" + text);' ' console.log(\"Bot: \" + reply);' ' console.log(\" enviados \" + sent.length + \" de \" + held + \" mensajes, marcas falsas: \" + badMarkers(reply).length);' '}' >> chatbot6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribir el chatbot, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const state = { history: [] };' ' // Muestras grabadas, usadas solo si ningún modelo responde.' ' await turn(state, \"¿Cuántos libros puedo llevarme?\", \"Puedes llevarte seis libros durante tres semanas [1].\");' ' await turn(state, \"¿Y cuánto cuesta un retraso?\", \"Un libro tarde cuesta diez céntimos al día [2].\");' ' await turn(state, \"¿A qué hora cierran el viernes?\", \"La biblioteca cierra a las cinco de la tarde en días laborables [3].\");' ' console.log(\"Guardado: \" + state.history.length + \" mensajes en chat.json\");' '}' 'main();' >> chatbot6.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Mantener un chat de tres turnos
docker exec -w /home/node/project agent node chatbot6.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Una salida posible
| Línea | Lo que puede mostrar |
|---|---|
| Tú y Bot | Tres preguntas, cada una con una respuesta corta que puede citar una fuente |
| enviados n de m | Con la muestra grabada: 1 de 1, 3 de 3 y 3 de 5. En el tercer turno se supera el presupuesto y se dejan fuera dos mensajes viejos. Con un modelo real, el último número puede cambiar |
| marcas falsas | Casi siempre 0. Un número mayor que 0 indica que la respuesta cita una fuente que no existe |
| Guardado | Guardado: 6 mensajes en chat.json |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.