Una respuesta con sus fuentes
La generación aumentada con recuperación (en inglés RAG) primero encuentra pasajes. Después pide a un modelo de lenguaje que responda solo con ellos. Importa porque un modelo que responde de memoria puede sonar seguro y equivocarse, mientras que uno que muestra sus fuentes se puede comprobar.
Alguien pregunta a qué hora abre la biblioteca el sábado. La web encuentra dos pasajes, pregunta a un modelo y muestra la respuesta con el pasaje del que sale.
Al final de esta lección
Nota
Puedo construir un prompt con lo que se encontró y responder solo a partir de eso.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Contexto | los pasajes que pones en el prompt | |
| Prompt | el texto que envías a un modelo | |
| Cita | una marca que apunta a un pasaje | [1] |
| Fundada | basada solo en los pasajes dados | |
| Negativa | una respuesta que dice que no sabe |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pon los pasajes en el prompt
El modelo no ha leído tus documentos. Por eso el prompt los lleva. Numera cada pasaje para que el modelo pueda señalar uno. Luego añade la pregunta y las reglas.
Pon pocos pasajes y cortos. Cada palabra de más se paga, y un prompt largo hace la respuesta más difícil de comprobar.
Ejemplo resuelto: las partes del prompt
| Parte | Ejemplo | Para qué sirve |
|---|---|---|
| Regla | Responde solo con los pasajes de abajo | Mantiene la respuesta fundada |
| Regla de cita | Cita cada pasaje que uses así: [1] | Hace la respuesta comprobable |
| Salida | Si no, responde exactamente: No lo sé | Evita una respuesta inventada |
| Pasajes | [1] El sábado la biblioteca abre a las 10:00 | Los únicos hechos permitidos |
| Pregunta | ¿A qué hora abre la biblioteca el sábado? | Lo que hay que responder |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Comprueba lo que vuelve
Un prompt pide. No obliga. El modelo puede citar un pasaje que no existe, añadir un hecho que no se le dio o olvidarse de citar.
Por eso el programa comprueba. Busca las marcas de cita y mira cada una. Una respuesta que no cita nada no se muestra como con fuente.
Ejemplo resuelto: qué hacer con cada respuesta
| La respuesta | Lo que hace el programa |
|---|---|
| Cita [1] y el pasaje 1 existe | Mostrarla con la fuente del pasaje 1 |
| Cita [7] y hay dos pasajes | No fiarse de ella |
| No cita nada | No mostrarla como con fuente |
| No lo sé. | Mostrar eso, sin fuentes |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pregunta una vez y comprueba la respuesta
Los siguientes Run escriben dos archivos y después hacen una sola llamada corta. El primero, model.js, envía el prompt al modelo del laboratorio. Si el modelo no responde, muestra Muestra grabada y usa una respuesta escrita en el script.
El segundo, answer.js, construye el prompt, pregunta, busca las citas y dice si la respuesta es una negativa. Nada personal va en un prompt.
Escribe 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.
Escribe 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.
Escribe el script de respuesta, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '// Los pasajes que encontró una recuperación, escritos aquí para que el Run cueste una llamada corta.' 'const passages = [' ' { source: \"horario-finde\", text: \"El sábado la biblioteca abre a las 10:00 y cierra a las 14:00. El domingo está cerrada.\" },' ' { source: \"horario-semana\", text: \"La biblioteca abre a las 9:00 y cierra a las 18:00 de lunes a viernes.\" },' '];' > answer.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de respuesta, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const question = \"¿A qué hora abre la biblioteca el sábado?\";' 'function buildPrompt(question, passages) {' ' const lines = [\"Responde solo con los pasajes de abajo. Cita cada pasaje que uses así: [1]. Si los pasajes no contienen la respuesta, responde exactamente: No lo sé.\", \"\"];' ' passages.forEach(function (one, i) { lines.push(\"[\" + (i + 1) + \"] \" + one.text); });' ' lines.push(\"\", \"Pregunta: \" + question);' ' return lines.join(\"\n\");' '}' >> answer.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de respuesta, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Muestra grabada, usada solo si ningún modelo responde.' 'const sample = \"Abre a las 10:00 el sábado [1].\";' 'function citedSources(reply) {' ' const found = [];' ' (reply.match(/\[(\d+)\]/g) || []).forEach(function (mark) {' ' const one = passages[Number(mark.slice(1, -1)) - 1];' ' if (one && found.indexOf(one.source) < 0) found.push(one.source);' ' });' ' return found;' '}' >> answer.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de respuesta, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const prompt = buildPrompt(question, passages);' ' console.log(prompt);' ' const reply = await ask([{ role: \"user\", content: prompt }], sample, 120);' ' console.log(\"Respuesta:\", reply);' ' const cited = citedSources(reply);' ' console.log(\"Fuentes:\", cited.length > 0 ? cited.join(\", \") : \"ninguna citada\");' ' console.log(\"Dice no lo sé:\", /no lo sé/i.test(reply) ? \"sí\" : \"no\");' '}' 'main();' >> answer.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecútalo y lee la respuesta
docker exec -w /home/node/project agent node answer.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 | Qué puede mostrar |
|---|---|
| El prompt | Las reglas, dos pasajes numerados y la pregunta |
| Respuesta | Abre a las 10:00 el sábado [1]. |
| Fuentes | horario-finde |
| Dice no lo sé | no |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que decirle a un modelo que use solo los pasajes hace que lo cumpla. La regla reduce el riesgo de una respuesta inventada. No lo elimina. Conserva la comprobación y cambia la pregunta por una que los pasajes no cubran, para ver funcionar la salida.