El teléfono que nadie dio
Extraer un registro de un texto es pedir a un modelo que lea un párrafo y rellene un conjunto fijo de campos. Importa porque el modelo tapará un hueco con una suposición si no le dices que no lo haga.
La secretaría de una facultad recibe una petición de aula con un nombre, un aula y una fecha, pero sin teléfono. La herramienta muestra un teléfono igualmente. Nadie lo dio: el modelo se lo inventó.
Al final de esta lección
Nota
Puedo convertir un párrafo en un registro sin inventar ningún valor.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Extraer | sacar datos con nombre de un texto | |
| Campo | un dato con nombre dentro de un registro | date |
| Null | el valor que dice que un campo no está | |
| Texto fuente | el párrafo de donde deben salir los datos | |
| Anclado | hallado en el texto fuente, no adivinado | |
| Completitud | la parte de campos que tienen valor |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Nombra los campos y permite null
Di exactamente qué campos quieres y el tipo de cada uno. Luego di qué hacer cuando un dato no está en el texto: usar null, sin adivinar nunca.
Sin esa regla, un modelo puede rellenar todos los campos, porque un registro lleno parece una buena respuesta. Dale permiso para dejar un hueco.
Ejemplo resuelto: un mensaje, cinco campos
| Campo | ¿Está en el mensaje? | El valor correcto |
|---|---|---|
| name | Sí: «me llamo María García» | María García |
| room | Sí: «el aula B12» | B12 |
| date | Sí: «2026-11-20» | 2026-11-20 |
| students | Sí: «24 estudiantes» | 24 |
| phone | No | null |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Comprueba que cada valor está anclado
No confíes solo en la instrucción. Después de la respuesta, compara cada valor con el texto fuente. Si el valor no aparece en él, trátalo como inventado y ponlo en null.
Esta comprobación es sencilla y tiene un punto débil: un número 2 de más aparecería dentro de 2026-11-20 y pasaría. Una comprobación más fina compara palabras enteras. Aun así detecta el teléfono inventado.
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 extractor, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' 'const source = \"Hola, me llamo María García. Quisiera reservar el aula B12 el 2026-11-20. Seremos 24 estudiantes y 3 profesores. Gracias por responder por correo.\";' 'const fields = [\"name\", \"room\", \"date\", \"students\", \"phone\"];' '' 'function pullJson(text) {' ' const start = text.indexOf(\"{\");' ' const end = text.lastIndexOf(\"}\");' ' if (start === -1 || end < start) return null;' ' try { return JSON.parse(text.slice(start, end + 1)); } catch (error) { return null; }' '}' > extract4.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 extractor, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Conservar un valor solo si aparece de verdad en el texto fuente.' 'function grounded(record, source, fields) {' ' const result = {};' ' for (const name of fields) {' ' const value = record === null ? null : record[name];' ' if (value === null || value === undefined || value === \"\") {' ' result[name] = null;' ' console.log(name + \": null (falta)\");' ' } else if (source.toLowerCase().includes(String(value).toLowerCase())) {' ' result[name] = value;' ' console.log(name + \": \" + value + \" (conservado)\");' ' } else {' ' result[name] = null;' ' console.log(name + \": \" + value + \" (no está en el texto, descartado)\");' ' }' ' }' ' return result;' '}' >> extract4.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 extractor, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const prompt = \"Extrae estos campos del mensaje como un objeto JSON: name (texto), room (texto), date (texto, año-mes-día), students (un número entero), phone (texto). Si un campo no está en el mensaje, usa null. No adivines nunca. Responde solo con el JSON.\n\nMensaje: \" + source;' '// Muestra grabada, usada solo si ningún modelo responde. Inventa un teléfono a propósito.' 'const sample = JSON.stringify({ name: \"María García\", room: \"B12\", date: \"2026-11-20\", students: 24, phone: \"612 345 678\" });' >> extract4.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 extractor, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const reply = await ask([{ role: \"user\", content: prompt }], sample, 150);' ' const record = grounded(pullJson(reply), source, fields);' ' let filled = 0;' ' for (const name of fields) {' ' if (record[name] === null) continue;' ' filled = filled + 1;' ' }' ' console.log(\"Rellenos: \" + filled + \" de \" + fields.length);' '}' 'main();' >> extract4.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecutar el extractor
docker exec -w /home/node/project agent node extract4.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 |
|---|---|
| name, room, date, students | Cada uno con un valor y (conservado) |
| phone | null (falta), o un valor marcado (no está en el texto, descartado) |
| Rellenos | Rellenos: 4 de 5 si el modelo solo halló los cuatro datos |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que un campo lleno es mejor que uno vacío. Un valor inventado se parece a uno real, y nadie piensa en comprobarlo. Un null dice con claridad que una persona debe mirarlo.