¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Trazar llamadas de IA con spans

Después de esta lección: Puedo trazar una petición a través de una función de IA y leer la traza para hallar lo lento, lo caro o lo fallido.

Lección de 70 min8 comandosLaboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Por qué tardó cinco segundos esa respuesta?

Un asistente de soporte planifica, llama a dos herramientas, vuelve a preguntar a un modelo y revisa su respuesta. El usuario espera cinco segundos, y la factura sale más alta de lo previsto. ¿Qué paso fue lento, y cuál se llevó el dinero?

El módulo 69 trazó una sola llamada. Esta lección va más allá: una ejecución de agente entera, con tokens, coste y fallos en cada paso.

Al final de esta lección

Nota

Puedo trazar una petición a través de una función de IA y leer la traza para hallar lo lento, lo caro o lo fallido.

Palabras clave

PalabraQué significaEjemplo
Identificador de spanel identificador de un solo spanspan_id
Identificador del padreel identificador del span que contiene a esteparent_span_id
Estadosi un span terminó bien o con un errorerror
Span de herramientaun span para una herramienta que llamó un agente
Recuento de tokenscuántos tokens entraron o salieron
Estándar abiertoun diseño común que cualquiera puede usar

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un span guarda identificadores, tiempos y datos

Cada span tiene un identificador de span y el identificador de traza de su petición. Nombra a su padre con un identificador del padre, que está vacío en la raíz.

Tiene un nombre, un inicio, un fin y un estado: ok o error. Los datos van en sus atributos. OpenTelemetry guarda los tiempos en nanosegundos desde 1970. Este laboratorio usa milisegundos desde el inicio, para que las cifras sean cortas.

Ejemplo resuelto: un span de modelo

CampoValor
trace_id7a3f0c1d00000000000000000000aa01
span_id0000000000000005
parent_span_id0000000000000001
namemodelo: respuesta
start y end3100 y 4900
statusok
attributesmodel m-small-2026-08, input_tokens 2100, output_tokens 300, fallback true

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Qué registra un span de modelo

Registra el modelo y su versión, porque una versión nueva puede cambiar la velocidad y el coste. Registra los tokens de entrada y de salida y el tiempo que tardó. Anota si la llamada se reintentó o pasó a otro modelo como alternativa.

Nunca pongas datos personales en un span en texto claro. El módulo 86, en la lección «Registrar con seguridad en una aplicación de IA», enseña a dejarlos fuera.

Un agente añade un span por cada herramienta

Un agente hace más que llamar a un modelo. Planifica, llama a herramientas y luego vuelve a preguntar al modelo. Da a cada llamada a una herramienta su propio span de herramienta, hijo del span de la petición.

Un span de herramienta registra el nombre de la herramienta, su tiempo y su estado. Una herramienta que falla aparece como un error dentro de una petición que aún puede terminar bien.

Escribe el ayudante del modelo, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > 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

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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 || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"estado \" + res.status);' '    const d = await res.json();' '    return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' '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 la traza del agente, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const TRACE = \"7a3f0c1d00000000000000000000aa01\";' 'const PRICES = { \"m-large-2026-09\": [3, 15], \"m-small-2026-08\": [1, 4] };' 'const spans = [];' 'function sid(n) { return String(n).padStart(16, \"0\"); }' 'function span(n, parent, name, start, end, status, attributes) {' '  spans.push({ trace_id: TRACE, span_id: sid(n), parent_span_id: parent ? sid(parent) : \"\",' '    name, start, end, status, attributes });' '}' > trace2.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 traza del agente, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'span(1, 0, \"petición del agente\", 0, 5000, \"ok\", {});' 'span(2, 1, \"modelo: plan\", 0, 800, \"ok\", { model: \"m-large-2026-09\", input_tokens: 900, output_tokens: 60 });' 'span(3, 1, \"herramienta: buscar pedido\", 800, 1100, \"ok\", { tool: \"find_order\" });' 'span(4, 1, \"herramienta: fecha de entrega\", 1100, 3100, \"error\", { tool: \"delivery_date\" });' 'span(5, 1, \"modelo: respuesta\", 3100, 4900, \"ok\", { model: \"m-small-2026-08\", input_tokens: 2100, output_tokens: 300, fallback: true });' 'span(6, 1, \"revisar respuesta\", 4900, 5000, \"ok\", {});' 'function kids(id) { return spans.filter(function (s) { return s.parent_span_id === id; }); }' 'function took(s) { return s.end - s.start; }' >> trace2.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 traza del agente, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function cost(a) {' '  const p = PRICES[a.model];' '  return p ? a.input_tokens * p[0] + a.output_tokens * p[1] : 0;' '}' 'function show(id, depth) {' '  for (const s of kids(id)) {' '    const flag = s.attributes.fallback ? \", con alternativa\" : \"\";' '    console.log(\"  \".repeat(depth) + s.name + \" \" + took(s) + \" ms \" + s.status + flag);' '    show(s.span_id, depth + 1);' '  }' '}' >> trace2.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 traza del agente, parte 4

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  console.log(\"traza \" + TRACE);' '  show(\"\", 0);' '  const leaves = spans.filter(function (s) { return kids(s.span_id).length === 0; });' '  leaves.sort(function (a, b) { return took(b) - took(a); });' '  console.log(\"paso más lento: \" + leaves[0].name + \", \" + took(leaves[0]) + \" ms\");' '  const priced = spans.filter(function (s) { return cost(s.attributes) > 0; });' '  priced.sort(function (a, b) { return cost(b.attributes) - cost(a.attributes); });' '  console.log(\"paso más caro: \" + priced[0].name + \", \" + cost(priced[0].attributes) + \" unidades\");' '  for (const s of spans.filter(function (one) { return one.status === \"error\"; })) {' '    const up = spans.find(function (one) { return one.span_id === s.parent_span_id; });' '    console.log(\"fallido: \" + s.name + \", dentro de \" + (up ? up.name : \"nada\"));' '  }' >> trace2.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 traza del agente, parte 5

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  const live = { trace_id: \"7a3f0c1d00000000000000000000aa02\", span_id: sid(7), parent_span_id: \"\",' '    name: \"modelo: comprobación en directo\", start: 0, end: 600, status: \"ok\", attributes: {} };' '  const messages = [{ role: \"user\", content: \"En una frase corta, ¿qué registra un span?\" }];' '  const reply = await ask(messages, \"Un span registra un paso cronometrado de una petición.\", 40);' '  live.attributes = { model: \"lab\", input_tokens: reply.inTokens, output_tokens: reply.outTokens, recorded: reply.recorded };' '  console.log(\"span en directo: \" + JSON.stringify(live));' '  console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> trace2.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta y lee la traza

Comando
docker exec -w /home/node/project agent node trace2.js

Se 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 Run

LíneaQué muestra
traza 7a3f0c1d00000000000000000000aa01El identificador de traza que comparten todos los spans
herramienta: fecha de entrega 2000 ms errorUn span de herramienta que falló
modelo: respuesta 1800 ms ok, con alternativaUn span de modelo que usó otro modelo
paso más lento: herramienta: fecha de entrega, 2000 msEl paso sin hijos que más duró
paso más caro: modelo: plan, 3600 unidadesLos tokens por el precio de su modelo
span en directo: {...}Una llamada real como span. Sus tokens son null cuando responde una muestra grabada

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Una traza se lee de tres formas

Para la velocidad, busca el paso sin hijos que más duró. Para el coste, multiplica los tokens de cada span de modelo por los precios de su modelo.

Para los fallos, lista cada span con estado error y el span que lo contiene. Un hijo fallido dentro de un span que terminó bien significa que el agente se recuperó.

Un error frecuente

Advertencia

Mucha gente cree que el paso más lento es también el más caro. Aquí la herramienta de fecha de entrega es la más lenta, pero la llamada de plan es la que más cuesta. La espera hace el tiempo y los tokens hacen el coste: revisa ambos antes de cambiar nada.

OpenTelemetry es un estándar abierto para trazas

OpenTelemetry es un estándar abierto para trazas, métricas y registros. Muchas herramientas leen su forma de span, así que un mismo registro sirve para más de un almacén.

También tiene nombres provisionales para los atributos de las llamadas de IA. Esos nombres aún están cambiando, así que toma los de la tabla como un ejemplo. Consulta la documentación actual antes de usar uno.

Nombres provisionales, consulta la documentación actual

Nuestra claveUn nombre provisionalQué guarda
modelgen_ai.request.modelEl modelo que se pidió
input_tokensgen_ai.usage.input_tokensLos tokens enviados
output_tokensgen_ai.usage.output_tokensLos tokens que salieron
toolgen_ai.tool.nameLa herramienta llamada
ninguna aquígen_ai.operation.nameEl tipo de llamada, por ejemplo chat

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Sobre este laboratorio y su código: Laboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.