Un chatbot es un bucle de llamadas
Un asistente de línea de comandos es un programa que lee una línea que escribes y la envía a un modelo de lenguaje. Imprime la respuesta y repite hasta que salgas. Es el programa útil más pequeño que se construye sobre un modelo.
Importa porque reúne todo lo anterior. Hay mensajes, un historial, un flujo y una llamada fallida que no debe romper el chat.
Al final de esta lección
Nota
Puedo juntar llamadas a un modelo en un programa que una persona pueda usar.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Bucle | código que se repite hasta que se le dice que pare | |
| Comando | una línea escrita que controla el programa, no el modelo | /salir |
| Historial | los mensajes anteriores que cada llamada reenvía | |
| Recortar | quitar los mensajes más antiguos para que el historial siga corto | |
| Salida limpia | irse a propósito, sin error y sin perder nada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El programa es un bucle con una lista
Cada turno hace los mismos pasos. Leer una línea. Añadirla al historial como mensaje user. Enviar los mensajes. Imprimir la respuesta. Añadir la respuesta al historial como mensaje assistant.
La lista es toda la memoria del programa. Si está mal, todas las respuestas siguientes también lo están.
Escribe la parte 1: el historial
docker exec -w /home/node/project agent node -e 'require("fs").writeFileSync("assistant.js", ["// assistant.js, parte 1: el historial y un límite a su tamaño.", "const lab = require(\"./lab.js\");", "const readline = require(\"readline\");", "const system = { role: \"system\", content: \"Eres un asistente útil. Responde con una o dos frases cortas.\" };", "const history = [];", "const LIMIT = 6;", "", "// Quedarse solo con los últimos mensajes, sin empezar nunca por un mensaje assistant.", "function trim() {", " while (history.length > LIMIT) history.shift();", " while (history.length > 0 && history[0].role === \"assistant\") history.shift();", "}"].join("\n") + "\n")'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Recorta el historial para que siga corto
Cada llamada envía todo el historial. Un chat largo cuesta más en cada turno y puede pasar del tamaño que el modelo sabe leer. Recórtalo: quédate solo con los últimos mensajes.
Tras el recorte, la lista no debe empezar por un mensaje assistant. Un chat empieza por el usuario, como viste en la primera lección.
Añade la parte 2: el flujo
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// Parte 2: enviar el historial e imprimir el flujo según llega.", "const { piecesOf } = require(\"./stream.js\");", "async function streamReply() {", " let reply = \"\";", " for await (const piece of piecesOf({ max_tokens: 120, messages: [system, ...history] })) {", " reply += piece;", " process.stdout.write(piece);", " }", " if (reply === \"\") throw new Error(\"no llegó ninguna palabra\");", " return reply;", "}"].join("\n") + "\n")'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Añade la parte 3: un historial en orden
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// Parte 3: mantener el historial en orden. Si la llamada falla, se quita la pregunta.", "async function answer(text) {", " history.push({ role: \"user\", content: text });", " try {", " const reply = await streamReply();", " process.stdout.write(\"\\n\");", " history.push({ role: \"assistant\", content: reply });", " trim();", " } catch (error) {", " history.pop();", " console.log(\"\\nMuestra grabada: no puedo llegar al modelo ahora, esta es una respuesta grabada. (\" + error.message + \")\");", " }", "}"].join("\n") + "\n")'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente deja la pregunta en el historial cuando una llamada falla. En el turno siguiente se envían dos mensajes user seguidos y el orden se rompe. Cuando una llamada falla, quita la pregunta, avisa y deja que la persona lo intente de nuevo.
Comandos y una salida limpia
Algunas líneas son para el programa, no para el modelo. Una línea que empieza por / es un comando: /salir se va, /borrar vacía el historial y /historial lo cuenta.
Busca los comandos antes de llamar al modelo. Así una persona siempre puede irse, haga lo que haga el modelo.
Añade la parte 4: el bucle
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// Parte 4: el bucle. Una línea que empieza por / es un comando.", "const lines = readline.createInterface({ input: process.stdin });", "const prompt = () => process.stdout.write(\"tú> \");", "(async () => {", " console.log(\"Asistente listo. Comandos: /salir, /borrar, /historial.\");", " prompt();", " for await (const line of lines) {", " const text = line.trim();", " if (!process.stdin.isTTY) console.log(text);", " if (text === \"/salir\") break;", " if (text === \"/borrar\") { history.length = 0; console.log(\"Historial borrado.\"); }", " else if (text === \"/historial\") console.log(history.length + \" mensajes guardados.\");", " else if (text !== \"\") { process.stdout.write(\"asistente> \"); await answer(text); }", " prompt();", " }", " console.log(\"Hasta luego.\");", " process.exit(0);", "})();"].join("\n") + "\n")'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Charla con un guion escrito
printf "Me llamo Marta.\n¿Cómo me llamo?\n/historial\n/borrar\n/historial\n/salir\n" | docker exec -i -w /home/node/project agent node assistant.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejemplo resuelto: lo que hizo el guion
| Escrito | Lo que hizo el programa |
|---|---|
| Me llamo Marta. | Envió 2 mensajes (el system y el suyo), imprimió una respuesta, guardó 2 |
| ¿Cómo me llamo? | Envió 4 mensajes (el system y los tres del historial), imprimió una respuesta, guardó 4 |
| /historial | Imprimió 4 mensajes guardados |
| /borrar | Vació el historial |
| /salir | Imprimió Hasta luego y salió |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Charla tú mismo con él
El guion de arriba se escribió por ti. Para charlar a mano, abre un terminal en el laboratorio y escribe esta línea: docker exec -it -w /home/node/project agent node assistant.js. Después escribe preguntas y termina con /salir.
Espera a que escribas, así que no es un botón. No escribas contraseñas ni nada personal: lo que escribes se envía a un modelo.
Para ir más lejos: cambia el asistente
Cambia el mensaje system de la parte 1 para darle una personalidad al asistente, y pon LIMIT a 4. Charla durante seis turnos.
Anota qué olvidó el asistente y cuándo. Di qué cuesta mantener un historial largo y qué harías al respecto.