Una clienta que cambia de opinión
Un usuario simulado es un script que hace de cliente en una conversación con tu asistente. Sirve porque un diálogo no tiene una única respuesta correcta, y escribir chats de prueba a mano no escala.
Marta pide un reembolso, nombra un pedido y luego cambia de opinión. ¿Lo sigue el asistente?
Al final de esta lección
Nota
Puedo probar un diálogo con un usuario guionizado y puntuar la conversación.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Usuario simulado | un script que hace de cliente |
| Personaje | quién es el usuario y cómo escribe |
| Objetivo | lo que quiere el usuario al final |
| Condición de fin | la regla que detiene el diálogo |
| Transcripción | toda la conversación, línea por línea |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Escribe al usuario como un script
Dale al usuario un personaje, un objetivo y una lista de líneas. Añade una condición de fin, como un adiós o un máximo de turnos, para que el diálogo siempre pare.
Un script da el mismo usuario en cada ejecución. Un modelo que hiciera de usuario variaría, gastaría crédito en cada línea y haría difícil repetir un fallo. Empieza con un script.
Ejemplo resuelto: las cuatro líneas de Marta
| Línea | Lo que dice Marta | Lo que prueba |
|---|---|---|
| 1 | hola, un reembolso por favor | si el asistente pregunta qué pedido |
| 2 | pedido A17 | si inicia el reembolso |
| 3 | mejor dicho, el pedido B02 | si maneja el cambio de opinión |
| 4 | gracias, adiós | si termina el diálogo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Puntúa la transcripción, no cada respuesta
Comprueba tres cosas en toda la transcripción. ¿Se logró el objetivo? ¿Los turnos están dentro del límite? ¿La acción ocurrió el número correcto de veces?
La última comprobación encuentra un fallo discreto. Un asistente que inicia un reembolso para A17 y otro para B02 logró el objetivo y reembolsó dos veces.
Escribe el ayudante 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 el ayudante 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 conductor del diálogo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '// Un usuario guionizado: un personaje, un objetivo, las líneas y un cambio de opinión en la línea 3.' 'const user = {' ' persona: \"Marta, con prisa, escribe mensajes cortos\",' ' goal: \"un reembolso del pedido B02\",' ' lines: [\"hola, un reembolso por favor\", \"pedido A17\", \"mejor dicho, el pedido B02\", \"gracias, adiós\"],' '};' '// Un asistente de sustitución hecho de reglas. Uno real sería el agente que se prueba.' 'function assistant(text) {' ' const order = text.match(/[AB][0-9][0-9]/);' ' if (order) return \"Reembolso iniciado para \" + order[0] + \".\";' ' if (text.includes(\"adiós\")) return \"Adiós.\";' ' return \"¿Cuál es el número de pedido?\";' '}' > sim.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 conductor del diálogo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function drive(maxTurns) {' ' const transcript = [];' ' for (let i = 0; i < user.lines.length && i < maxTurns; i = i + 1) {' ' transcript.push(\"usuario: \" + user.lines[i]);' ' const reply = assistant(user.lines[i]);' ' transcript.push(\"asistente: \" + reply);' ' if (reply.includes(\"Adiós\")) break;' ' }' ' return transcript;' '}' 'const transcript = drive(6);' 'transcript.forEach(function (line) { console.log(line); });' 'const refunds = transcript.filter(function (line) { return line.includes(\"Reembolso iniciado\"); });' >> sim.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 conductor del diálogo, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'console.log(\"Objetivo logrado: \" + transcript.join(\" \").includes(\"Reembolso iniciado para B02\"));' 'console.log(\"Turnos usados: \" + transcript.filter(function (line) { return line.startsWith(\"usuario\"); }).length);' 'console.log(\"Reembolsos iniciados: \" + refunds.length + \" (el usuario quería uno)\");' 'async function main() {' ' const system = \"Eres un asistente de soporte de una tienda. Pide el número de pedido antes de reembolsar nada. Responde con brevedad.\";' ' const sample = \"Claro. ¿Cuál es el número de pedido?\";' ' const text = await ask([{ role: \"system\", content: system }, { role: \"user\", content: user.lines[0] }], sample, 60);' ' console.log(\"Respuesta en vivo: \" + text.trim());' ' console.log(\"Pregunta por el pedido: \" + text.toLowerCase().includes(\"pedido\"));' '}' 'main();' >> sim.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Conduce al asistente con Marta
docker exec -w /home/node/project agent node sim.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el conductor
| Línea | Lo que muestra |
|---|---|
| Transcripción | Cuatro líneas del usuario y cuatro del asistente |
| Objetivo logrado | true |
| Turnos usados | 4 |
| Reembolsos iniciados | 2, el usuario quería uno |
| Respuesta en vivo | Una salida posible: una pregunta sobre el pedido |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo
Consejo
En sim.js quita la tercera línea, "mejor dicho, el pedido B02", y lanza otra vez. Los reembolsos iniciados bajan a 1, pero el objetivo ya no se logra. Explica por qué.
Un error frecuente
Advertencia
Mucha gente cree que un diálogo aprueba cuando su última respuesta suena bien. Un adiós amable puede seguir a dos reembolsos. Puntúa lo que hizo el asistente en toda la transcripción: el objetivo, las acciones y los turnos.