Una llamada toma una lista de mensajes
Una llamada de chat a un modelo de lenguaje toma una lista de mensajes. Cada mensaje tiene un rol y un texto. El modelo lee toda la lista y escribe el mensaje siguiente.
Esto importa porque un modelo no recuerda nada entre una llamada y otra. Un chat que parece tener memoria es un programa que vuelve a enviar los mensajes anteriores cada vez.
Al final de esta lección
Nota
Puedo construir la lista de mensajes que toma una llamada a un modelo de lenguaje.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Mensaje | un turno del chat: un rol y un texto | |
| Rol | quién escribe un mensaje | user |
| Mensaje system | la instrucción que fija cómo se comporta el modelo | |
| Historial | los mensajes anteriores, que se reenvían en cada llamada | |
| Cuerpo de la petición | el JSON que un programa envía con una llamada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un mensaje es un rol y un texto
Un mensaje es un objeto con dos claves. role dice quién habla y content guarda las palabras.
Hay tres roles. El mensaje system dice cómo debe comportarse el modelo. Un mensaje user es lo que dice la persona. Un mensaje assistant es algo que el modelo dijo antes.
El cuerpo de la petición de una llamada
{
"model": "lab",
"max_tokens": 60,
"messages": [
{ "role": "system", "content": "Respondes con una sola frase corta." },
{ "role": "user", "content": "¿Qué es una variable en programación?" }
]
}Envía una llamada al modelo del laboratorio
docker exec -w /home/node/project agent node -e '/* El cuerpo de la petición: un nombre de modelo, un límite de respuesta y una lista de mensajes. */ const body = { model: "lab", max_tokens: 60, messages: [ { role: "system", content: "Respondes con una sola frase corta." }, { role: "user", content: "¿Qué es una variable en programación?" } ] }; (async () => { try { const url = (process.env.AI_URL || "http://ai:4000") + "/v1/chat/completions"; const answer = await fetch(url, { method: "POST", body: JSON.stringify(body), headers: { "content-type": "application/json", authorization: "Bearer lab" } }); if (!answer.ok) throw new Error("HTTP " + answer.status); const data = await answer.json(); console.log("Respuesta del modelo: " + data.choices[0].message.content); } catch (error) { console.log("Ningún modelo respondió (" + error.message + ")."); console.log("Muestra grabada: Una variable guarda un valor que el programa puede cambiar."); } })();'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
El modelo no recuerda nada entre llamadas
Cada llamada es independiente. El modelo solo ve los mensajes de esa llamada.
Para mantener una conversación, tu programa guarda una lista. Tras cada respuesta añade la pregunta y la respuesta. En la llamada siguiente reenvía el historial, con la pregunta nueva al final.
Escribe el archivo de ayuda lab.js
docker exec -w /home/node/project agent node -e 'require("fs").writeFileSync("lab.js", ["// lab.js, parte 1: el único sitio que envía una llamada al modelo del laboratorio.", "const base = process.env.AI_URL || \"http://ai:4000\";", "", "async function send(body, timeout) {", " return fetch(base + \"/v1/chat/completions\", {", " method: \"POST\",", " headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },", " body: JSON.stringify({ model: \"lab\", ...body }),", " signal: AbortSignal.timeout(timeout || 30000),", " });", "}"].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 preguntar con una muestra grabada
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("lab.js", ["", "// Parte 2: preguntar una vez. Si el modelo no puede responder, devuelve la muestra grabada.", "async function ask(body, sample) {", " try {", " const answer = await send(body);", " if (!answer.ok) throw new Error(\"HTTP \" + answer.status);", " const data = await answer.json();", " return { label: \"Respuesta del modelo\", text: data.choices[0].message.content, data: data };", " } catch (error) {", " return { label: \"Muestra grabada\", text: sample, note: error.message };", " }", "}", "", "module.exports = { send, ask };"].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.
Pregunta con el historial y luego sin él
docker exec -w /home/node/project agent node -e 'const lab = require("./lab.js"); const system = { role: "system", content: "Responde con una frase corta." }; const history = [ { role: "user", content: "Me llamo Marta." }, { role: "assistant", content: "Encantado, Marta." } ]; const question = { role: "user", content: "¿Cómo me llamo?" }; (async () => { const withHistory = await lab.ask({ max_tokens: 30, messages: [system, ...history, question] }, "Te llamas Marta."); console.log("Con el historial, " + withHistory.label + ": " + withHistory.text); const without = await lab.ask({ max_tokens: 30, messages: [system, question] }, "No sé cómo te llamas."); console.log("Sin él, " + without.label + ": " + without.text); console.log("Mensajes enviados en cada llamada: " + [system, ...history, question].length + " y " + [system, question].length); })();'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 cree que el modelo recuerda la llamada anterior. No es así. La segunda llamada, sin historial, no podía saber el nombre. Si quieres que el modelo use algo, ponlo en los mensajes que envías.
Ejemplo resuelto: lo que llevó cada llamada
| Llamada | Mensajes enviados | Lo que el modelo puede saber |
|---|---|---|
| Con el historial | system, user, assistant, user (4) | El nombre Marta |
| Sin él | system, user (2) | Nada sobre Marta |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El orden de los mensajes importa
Pon primero el mensaje system. Después los turnos se alternan: user, assistant, user, y así. El último mensaje es del usuario, porque es lo que el modelo responde.
La mayoría de las API de modelos rechazan una lista que rompe este orden, o dan respuestas raras. Comprueba el orden en tu código antes de enviar.
Para ir más lejos: cambia el mensaje system
Abre el Run del historial y cambia el mensaje system por «Responde en inglés». Pulsa Run otra vez.
Anota qué cambió y qué no. Después di en una frase por qué la instrucción tenía que estar en los mensajes que enviaste.