El prompt es código
En resumen: Un prompt es el texto que da al modelo un rol, reglas y datos; una plantilla con ejemplos y comprobaciones de la respuesta vive, como cualquier otro código, en el código, en git y en las pruebas.
Problema
La respuesta que suena bien
Un cliente escribe en el chat de la tienda: «¿Cuándo llega el pedido 1042?» El asistente está conectado a un modelo como en la lección sobre la llamada a la API, y el prompt de sistema es una sola línea:
const client = new FakeClient();const order = { id: "1042", status: "enviado", eta: "3 de noviembre" };const question = [{ role: "user", content: "¿Cuándo llega el pedido 1042?" }];const reply = client.create("Eres un asistente de una tienda.", question);console.log(`prompt: ~${reply.inputTokens} tokens`);console.log(`respuesta: ${reply.text}`);for (const line of check(reply.text, order)) { console.log(` ${line}`);}prompt: ~15 tokensrespuesta: ¡Hola! Gracias por comunicarse con nosotros. Normalmente la entrega tarda de 2 a 5 días hábiles, según el mensajero y la dirección. El pedido 1042 muy probablemente llegará antes del viernes. Si tiene más preguntas, con gusto le ayudo. ✗ como máximo 30 palabras (39) ✗ la fecha de los datos (3 de noviembre) ✓ el número del pedido (1042)La respuesta es cortés, pero tiene 39 palabras, donde el chat tiene sitio para dos frases. «De 2 a 5 días hábiles» es genérico, y «antes del viernes» es inventado: en la base de datos el pedido llega el 3 de noviembre. El cliente esperará el viernes y luego escribirá otra vez, enfadado.
El modelo no hizo nada mal que pudiera haber hecho bien. No sabe nada del pedido 1042 y escribe el texto más verosímil, como en la lección sobre los límites del modelo. El problema es el prompt.
La idea
El prompt es una especificación
El prompt le dice al modelo qué hacer, igual que la descripción de una tarea se lo dice a un compañero nuevo. Un buen prompt tiene cinco partes:
- Rol: quién es el modelo («el asistente de soporte de la tienda»).
- Reglas: cuánto debe extenderse, con qué tono, qué no está permitido.
- Datos: todo lo que el modelo necesita saber, puesto ahí por el programa. Aquí es el pedido de la base de datos.
- Un borde alrededor de los datos: etiquetas como
<order>y</order>muestran dónde terminan las instrucciones y dónde empiezan los datos. - Qué hacer cuando no sabe: «si los datos no bastan, pasa la consulta a un compañero». Sin esta regla el modelo rellena el hueco con invenciones.
El prompt lo construye una función con parámetros, no se escribe a mano para cada pregunta. Esa función es una plantilla de prompt (prompt template). Escribir y probar esos prompts es prompt engineering: el prompt es código que ejecuta el modelo.
Míralo
La plantilla
La plantilla recibe el pedido y devuelve el prompt de sistema:
function supportPrompt(order) { return `Eres el asistente de soporte de una tienda.Responde con cortesía, tratando de «usted», en dos frases como máximo.Usa solo los datos entre <order> y </order>.Si los datos no bastan, pasa la consulta a un compañero.No prometas fechas que no estén en los datos.
<order>número: ${order.id}estado: ${order.status}entrega prevista: ${order.eta}</order>`;}const order = { id: "1042", status: "enviado", eta: "3 de noviembre" };const system = supportPrompt(order);console.log(system);console.log(`~${tokens(system)} tokens`);Eres el asistente de soporte de una tienda.Responde con cortesía, tratando de «usted», en dos frases como máximo.Usa solo los datos entre <order> y </order>.Si los datos no bastan, pasa la consulta a un compañero.No prometas fechas que no estén en los datos.
<order>número: 1042estado: enviadoentrega prevista: 3 de noviembre</order>~76 tokensLos datos no vienen del modelo, sino del programa: el estado y la fecha se leen de la base de datos. Si mañana el pedido cambia de estado, el prompt cambia con él.
El precio: el prompt vago tenía unos 8 tokens, este tiene unos 76. Cada pregunta del chat paga estos tokens otra vez, porque la API no tiene memoria y el prompt va con cada solicitud. Qué es un token y por qué el precio se da en tokens, lo viste en la lección sobre los tokens.
Míralo
Una pregunta, dos prompts
const vague = "Eres un asistente de una tienda.";const prompts = [vague, supportPrompt(order)];for (const system of prompts) { const reply = client.create(system, question); console.log(`~${reply.inputTokens} tokens`); console.log(reply.text); for (const line of check(reply.text, order)) { console.log(` ${line}`); }}function supportPrompt(order) { return `Eres el asistente de soporte de una tienda.Responde con cortesía, tratando de «usted», en dos frases como máximo.Usa solo los datos entre <order> y </order>.Si los datos no bastan, pasa la consulta a un compañero.No prometas fechas que no estén en los datos.
<order>número: ${order.id}estado: ${order.status}entrega prevista: ${order.eta}</order>`;}Paso a paso, en palabras
- Una pregunta de un cliente: «¿Cuándo llega el pedido 1042?» Primero el prompt vago: una línea, sin datos, ~15 tokens.
- La respuesta es cortés y larga. «Antes del viernes» es inventado: en el prompt no hay ninguna fecha de la que el modelo pueda sacar el plazo.
- El programa revisa la respuesta: 39 palabras, sin la fecha de los datos. Pasa 1 de 3.
- La plantilla: quién es el modelo, cuánto debe extenderse, qué puede usar y qué hacer cuando los datos no alcanzan.
- Los datos del pedido los pone el programa, entre <order> y </order>. El prompt ya tiene ~83 tokens en vez de ~15.
- La misma pregunta, el prompt nuevo: una frase con la fecha 3 de noviembre.
- El prompt vago pasa 1 revisión de 3, el claro 3 de 3. El prompt claro es más largo, pero su respuesta se puede comprobar.
El programa envía la misma pregunta con los dos prompts y revisa cada respuesta con tres reglas: la longitud, la fecha de los datos y el número del pedido:
function check(answer, order) { const words = answer.trim().split(/\s+/).length; return [ `${mark(words <= 30)} como máximo 30 palabras (${words})`, `${mark(answer.includes(order.eta))} la fecha de los datos (${order.eta})`, `${mark(answer.includes(order.id))} el número del pedido (${order.id})`, ];}
function mark(ok) { return ok ? "✓" : "✗";}const client = new FakeClient();const order = { id: "1042", status: "enviado", eta: "3 de noviembre" };const question = [{ role: "user", content: "¿Cuándo llega el pedido 1042?" }];const vague = "Eres un asistente de una tienda.";const prompts = [vague, supportPrompt(order)];for (const system of prompts) { const reply = client.create(system, question); console.log(`~${reply.inputTokens} tokens`); console.log(reply.text); for (const line of check(reply.text, order)) { console.log(` ${line}`); }}~15 tokens¡Hola! Gracias por comunicarse con nosotros. Normalmente la entrega tarda de 2 a 5 días hábiles, según el mensajero y la dirección. El pedido 1042 muy probablemente llegará antes del viernes. Si tiene más preguntas, con gusto le ayudo. ✗ como máximo 30 palabras (39) ✗ la fecha de los datos (3 de noviembre) ✓ el número del pedido (1042)~83 tokensEl pedido 1042 está enviado y debería llegar el 3 de noviembre. ✓ como máximo 30 palabras (12) ✓ la fecha de los datos (3 de noviembre) ✓ el número del pedido (1042)El prompt vago pasa 1 revisión de 3, el claro 3 de 3. Las revisiones son código: no dicen si la respuesta es agradable, solo si cumple las reglas que se pueden comprobar. Cómo estas revisiones se convierten en un conjunto de pruebas para toda la aplicación está en la lección sobre la evaluación.
Con honestidad sobre el modelo en los programas. Aquí el modelo es un cliente falso: la misma forma que la API real (prompt de sistema, mensajes, respuesta y tokens), pero las respuestas están grabadas. Su regla es burda: si el prompt tiene datos del pedido, responde a partir de ellos; si no, devuelve la respuesta genérica típica. Así el programa corre sin red y da la misma salida cada vez. Un modelo real da otras palabras en cada ejecución, pero la diferencia entre los dos prompts es la misma. El código del cliente falso está abajo, en «Más precisamente, para curiosos».
Pruébalo
Encuentra el error
Un compañero reescribe la plantilla para la pregunta «¿dónde están mis pedidos?» y le pasa todos los pedidos de la base de datos. Las pruebas pasan: las respuestas son cortas y tienen las fechas correctas. ¿En qué línea está el error?
Elige la línea con el error.
El bucle recorre los pedidos de todos los clientes, y customer no se usa nunca. Lo que está en el prompt, el modelo puede decirlo: el cliente pregunta «¿y mi otro pedido?» y oye el número y el estado del de otra persona. El arreglo está en el código, no en una regla «no reveles datos ajenos»: el bucle toma solo los pedidos de este cliente. Cuantos menos datos tenga el prompt, menos puede filtrarse, y además el prompt es más barato.
La idea
Ejemplos en vez de explicaciones
A veces la respuesta no va a un cliente, sino a código. Los correos de la bandeja de soporte deben ordenarse por tema: entrega, devolución, pago u otro. El programa espera exactamente una de las cuatro palabras, para poner el correo en la cola correcta.
Puedes explicar el formato con palabras («responde solo con la categoría»). Es más seguro mostrarlo: unos mensajes de clientes, cada uno con la respuesta que esperas. Van en el historial de la conversación antes del correo real, como turnos anteriores del modelo.
Esto se llama ejemplos en el prompt (few-shot). El modelo continúa el texto según el patrón que ve, así que los ejemplos sostienen el formato mejor que las explicaciones. Por qué, lo sabes por la lección sobre el siguiente token: el modelo da una probabilidad a cada continuación, y los ejemplos hacen que el formato exacto sea el más probable.
Míralo
Tres ejemplos, cuatro correos
La instrucción y las categorías permitidas:
const SYSTEM = "Determina el tema del mensaje del cliente. " + "Categorías: entrega, devolución, pago, otro. " + "Responde solo con la categoría.";const LABELS = ["entrega", "devolución", "pago", "otro"];Los tres ejemplos son pares de mensajes: el cliente (user) y la respuesta que queremos (assistant). El último mensaje es el correo real:
function withExamples(message) { return [ { role: "user", content: "Mi paquete todavía no llegó y ya pasó una semana." }, { role: "assistant", content: "entrega" }, { role: "user", content: "Quiero devolver los zapatos, me quedan pequeños." }, { role: "assistant", content: "devolución" }, { role: "user", content: "Me cobraron dos veces en la tarjeta." }, { role: "assistant", content: "pago" }, { role: "user", content: message }, ];}El programa acepta solo una categoría exacta. Permite un poco de ruido: espacios, mayúsculas y un punto al final:
function parseLabel(answer) { let label = answer.trim().toLowerCase(); if (label.endsWith(".")) label = label.slice(0, -1); return LABELS.includes(label) ? label : null;}Los mismos cuatro correos sin ejemplos y con ejemplos:
const client = new FakeClient();const inbox = [ "El mensajero no me encontró en la dirección.", "¿Cómo puedo devolver la chaqueta si le quité la etiqueta?", "El pago con tarjeta no se procesa.", "¿Tienen una tienda en Madrid?",];for (const examples of [false, true]) { console.log(examples ? "con ejemplos:" : "sin ejemplos:"); let parsed = 0; for (const message of inbox) { const messages = examples ? withExamples(message) : [{ role: "user", content: message }]; const answer = client.create(SYSTEM, messages).text; const label = parseLabel(answer); if (label !== null) parsed++; console.log(` "${answer}" → ${label ?? "?"}`); } console.log(` reconocidos: ${parsed} de ${inbox.length}`);}sin ejemplos: "Categoría: entrega" → ? "Devolución del producto" → ? "Pago." → pago "Esto no es ninguna de las categorías, diría «otro»." → ? reconocidos: 1 de 4con ejemplos: "entrega" → entrega "devolución" → devolución "pago" → pago "otro" → otro reconocidos: 4 de 4Sin ejemplos el modelo acierta el tema, pero lo envuelve en palabras, y el programa lee solo «Pago.». Con tres ejemplos las respuestas tienen justo el formato necesario: 4 de 4.
Pruébalo
Predice la salida
¿Qué imprime parseLabel para estas tres respuestas?
for (const answer of [" Entrega.", "Categoría: entrega", "devolución"]) { console.log(parseLabel(answer));}« Entrega.» pasa: los espacios se van, las letras pasan a minúsculas, el punto del final se corta. «Categoría: entrega» contiene la categoría, pero no es una categoría, así que es null. El programa no adivina lo que el modelo quiso decir. Una respuesta que no puede leer va a una persona.
Pruébalo
¿Qué falta?
Un ejemplo es un par de mensajes. ¿Qué rol tiene el segundo, la respuesta que queremos ver?
{ role: "user", content: "Mi paquete todavía no llegó y ya pasó una semana." },
{ role: "", content: "entrega" },assistant. El ejemplo finge ser una respuesta anterior del modelo. Si las dos líneas son user, el modelo ve dos preguntas del cliente y nada que copiar.
La idea
El prompt es código
Una palabra del prompt cambia el comportamiento de toda la aplicación, igual que una línea de código. Por eso lo tratas como código:
- En el código, no en la configuración. La plantilla es una función o una constante del proyecto, no un texto que alguien edita en el servidor. Así está en git: ves quién cambió qué y puedes recuperar la versión anterior.
- Los datos aparte de las instrucciones. Las reglas van en el prompt de sistema, los datos entre etiquetas, y la pregunta del cliente es un mensaje aparte con el rol
user. El texto del cliente nunca se pega dentro de las instrucciones. - Una comprobación antes del lanzamiento. Un cambio en el prompt que arregla una respuesta puede romper otra. Un conjunto de preguntas con comprobaciones que ejecutas en cada cambio está en la lección sobre la evaluación.
- Lo que puede hacer el código, lo hace el código. La fecha de entrega, el total y el plazo de devolución se calculan en el programa y van al prompt ya listos. El modelo los cuenta; no los calcula.
La respuesta de un modelo real cambia de una ejecución a otra. Por eso las comprobaciones miran reglas (longitud, fecha, formato), no palabras exactas.
La idea
Cuándo duele
- Un prompt largo se paga cada vez. 76 tokens por 10 000 preguntas al día son 760 000 tokens solo por el prompt. Pon en el prompt lo que necesita esta pregunta, no todo el manual de la tienda.
- Muchas reglas pelean. «Responde con brevedad» y «explica los términos en detalle» en un mismo prompt dan una elección al azar entre las dos. Cuando las reglas llegan a veinte, el modelo se queda solo con algunas.
- Los ejemplos tiran. Si los tres ejemplos son sobre la entrega, el modelo dice «entrega» más a menudo. Los ejemplos deben cubrir todos los casos y no ser todos iguales.
- El prompt no es una cerradura. «Nunca reveles datos ajenos» es una petición, no una protección. Lo que está en el prompt puede salir, por eso solo contiene lo que esta persona puede ver. El texto del cliente es entrada no confiable; cómo proteger una aplicación de las instrucciones escondidas en ella está en la lección sobre prompt injection del módulo «Agentes».
- El prompt no arregla datos malos. Si la fecha de la base de datos es errónea, el mejor prompt la repite tal cual.
Más precisamente, para curiosos
La llamada real (pruébala en tu computadora). Este programa no forma parte de la comprobación de la lección: necesitas una cuenta con un proveedor de modelos, una clave y dinero por cada llamada. La clave está en la variable de entorno LLM_API_KEY, nunca en el código. Por qué, está en la lección sobre secretos en el código y en git. Los proveedores ofrecen SDK oficiales para Java, C#, Python y JavaScript con la misma forma (mensajes, roles, tokens, herramientas); los nombres exactos de clases y métodos están en la documentación del proveedor. Aquí está la solicitud como JSON, con los nombres de campo habituales y un marcador en lugar del nombre del modelo:
{ "model": "<nombre del modelo>", "max_tokens": 1024, "system": "Eres el asistente de soporte de una tienda.\nResponde con cortesía, tratando de «usted», en dos frases como máximo.\nUsa solo los datos entre <order> y </order>.\nSi los datos no bastan, pasa la consulta a un compañero.\nNo prometas fechas que no estén en los datos.\n\n<order>\nnúmero: 1042\nestado: enviado\nentrega prevista: 3 de noviembre\n</order>", "messages": [ { "role": "user", "content": "¿Cuándo llega el pedido 1042?" } ]}Un ejemplo de respuesta: los bloques en content, stop_reason y los tokens en usage:
{ "content": [ { "type": "text", "text": "El pedido 1042 está enviado y debería llegar el 3 de noviembre." } ], "stop_reason": "end_turn", "usage": {"input_tokens": 83, "output_tokens": 16}}Sin SDK, la solicitud sale con el cliente HTTP estándar del lenguaje. La dirección es un marcador:
// requestJson es el JSON de la solicitud de arriba.// La cabecera exacta de la clave está en la documentación del proveedor.const response = await fetch("https://<proveedor>/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}`, "Content-Type": "application/json", }, body: requestJson,});console.log(await response.text());La única diferencia con la lección sobre la llamada es system: en vez de una línea de texto es el resultado de la plantilla.
El cliente falso por dentro. Conserva la forma de la solicitud y de la respuesta. Cuenta los tokens a grandes rasgos: palabras × 1,3, redondeado hacia arriba. El número real depende del tokenizador y cambia de un idioma a otro.
// Hace las veces del modelo real: devuelve una respuesta grabada,// sin red y siempre la misma.class FakeClient { static VAGUE = "¡Hola! Gracias por comunicarse con nosotros. Normalmente la entrega tarda de 2 a 5 días hábiles, según el mensajero y la dirección. El pedido 1042 muy probablemente llegará antes del viernes. Si tiene más preguntas, con gusto le ayudo."; static VERBOSE = { "entrega": "Categoría: entrega", "devolución": "Devolución del producto", "pago": "Pago.", "otro": "Esto no es ninguna de las categorías, diría «otro».", };
create(system, messages) { const text = this.answer(system, messages); let input = tokens(system); for (const m of messages) input += tokens(m.content); return { text, stopReason: "end_turn", inputTokens: input, outputTokens: tokens(text) }; }
answer(system, messages) { const last = messages.at(-1).content; if (system.includes("Categorías:")) { const label = labelOf(last); return messages.length > 1 ? label : FakeClient.VERBOSE[label]; } const eta = field(system, "entrega prevista: "); if (eta === null) return FakeClient.VAGUE; const number = field(system, "número: "); const status = field(system, "estado: "); return `El pedido ${number} está ${status} y debería llegar el ${eta}.`; }}
// A grandes rasgos: palabras × 1,3, redondeado hacia arriba.function tokens(text) { const t = text.trim(); return Math.floor(((t ? t.split(/\s+/).length : 0) * 13 + 9) / 10);}
function labelOf(message) { const m = message.toLowerCase(); if (m.includes("mensajero") || m.includes("paquete")) return "entrega"; if (m.includes("devolver")) return "devolución"; if (m.includes("tarjeta") || m.includes("pago")) return "pago"; return "otro";}
function field(system, name) { const line = system.split("\n").find((l) => l.startsWith(name)); return line === undefined ? null : line.slice(name.length);}Por qué etiquetas. <order> y </order> no son una sintaxis especial. El modelo ha visto mucho texto con esas etiquetas y entiende dónde empieza y termina el bloque. ### o las comillas funcionan igual de bien, mientras sean las mismas en todas partes.
«Piensa paso a paso». En tareas con varios pasos (una suma, una comparación) ayuda que el modelo escriba primero su razonamiento y luego la respuesta. Eso cuesta más tokens y tiempo, y la respuesta hay que separarla del razonamiento antes de que llegue al programa. La salida estructurada de la siguiente lección hace fiable esa separación.
Una plantilla literal con acentos graves conserva la sangría literalmente. Por eso las líneas de la plantilla no tienen sangría aunque estén dentro de una función.Ejercicio
Una plantilla para devoluciones
Resuelve la tarea en tu editor.
Escribe una plantilla returnPrompt(order) para las preguntas sobre devoluciones. El pedido tiene un número, una fecha de entrega (o ninguna, si todavía no se ha entregado) y un total en euros. La plantilla tiene tres reglas y los datos entre <order> y </order>. Si no hay fecha, en los datos dice «todavía no se ha entregado»: el código decide qué decir, no el modelo. Ejecútala para los dos pedidos e imprime --- después de cada prompt.
Eres el asistente de soporte de una tienda en línea.El cliente puede devolver el pedido hasta 14 días después de la entrega.Usa solo los datos entre <order> y </order>.
<order>número: 1187entregado el: 21.10total: 89.90 €</order>---Eres el asistente de soporte de una tienda en línea.El cliente puede devolver el pedido hasta 14 días después de la entrega.Usa solo los datos entre <order> y </order>.
<order>número: 1203entregado el: todavía no se ha entregadototal: 34.50 €</order>---Más tareas
- El plazo en el código. Añade a los datos una línea «se puede devolver hasta:», con la fecha 14 días después de la entrega, calculada en el programa.
- Una comprobación más. Añade a
checkla regla «sin promesa de un día de la semana»: la respuesta no contiene «lunes», «martes» y así hasta «domingo». ¿Cuántas comprobaciones pasa entonces la respuesta vaga?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Predecir el siguiente token · La ventana de contexto · Los límites del modelo · Llamar a un modelo a través de una API
- Continúa en: Salida estructurada: JSON según un esquema · Uso de herramientas: el modelo pide, el programa actúa · Evaluación: un conjunto de pruebas para el asistente · RAG: respuestas a partir de los documentos
- Ejercicios sobre el tema: Aplicaciones con LLM