Ir al contenido
mcpd
sin daemon
Código

Puntuaciones y feedback

Una puntuación es lo que pensó una máquina. El feedback es lo que pensó una persona. mcpd las guarda como dos señales distintas y nunca promedia una con la otra.

Dos señales, no un número

Una puntuación es una evaluación automática: un modelo juez, una expresión regular, una prueba unitaria, cualquier cosa capaz de producir un número sobre una ejecución. Un registro de feedback es el juicio de una persona: un pulgar abajo en tu producto, la valoración de un revisor, una respuesta corregida.

Ambos se enganchan a un trace_id o a un span_id, ambos llegan como señales por derecho propio y cada uno se guarda en su propia tabla. Nunca se combinan. Un juez que puntúa 0.9 en relevancia y un cliente que pulsa el pulgar abajo son dos afirmaciones ciertas, y el desacuerdo entre ellas es lo más útil que hay en esa ejecución. Un promedio borraría justo eso y te dejaría con un número sobre el que nadie puede actuar.

  • Spans

    What ran, nested, with timings

  • Logs

    Lines the run emitted

  • Metrics

    Numbers, with exemplars back to a run

  • Scores

    Machine evaluation

  • Feedback

    What a person thought

  • Aspect notes

    A sentence a model wrote about the run

trace_id Store Search Export

Una puntuación declara su escala

min y max forman parte de la puntuación. Sin ellos el valor no se puede interpretar: 0.86 puede ser un buen resultado en una escala de 0 a 1, uno malo en una escala de 0 a 5, o una distancia donde menos es mejor. Seis meses después, con dos evaluadores en el mismo proyecto, nadie sabe cuál era. La especificación les da 0 y 1 por defecto, y todos los SDK los exigen en el punto de llamada para que ese valor por defecto nunca se acepte por descuido.

CampoPara qué sirve
scorer_id Qué evaluador la produjo, por ejemplo answer_relevance. Obligatorio.
scorer_version Una puntuación solo se compara con otra de la misma versión. Si cambias el prompt de un juez tienes un evaluador nuevo, no la continuación del anterior.
value El número.
min, max La escala declarada sobre la que se sitúa el número.
label Un resultado categórico, cuando el evaluador produce uno junto al número.
reason El porqué. En un modelo juez es su explicación, y es lo que convierte una puntuación en algo discutible en lugar de en magia.
source En vivo, fuera de línea, de experimento o manual.
score_trace_id La traza de la propia ejecución de puntuación, para poder abrir y leer una puntuación sospechosa como cualquier otra ejecución.
cost Lo que costó producir la puntuación.

Cómo registrar una

El ayudante del SDK encola una puntuación igual que encola un span, así que la llamada no bloquea y no puede lanzar una excepción. Si no le pasas traceId, se engancha a la traza del span activo.

puntuación y feedback

// A machine evaluation of the run. `min` and `max` are required: 0.86 on an
// undeclared scale cannot be compared to anything.
mcpd.score({
  traceId,
  scorerId: 'answer_relevance',
  value: 0.86,
  min: 0,
  max: 1,
  reason: 'Answered the question and gave the delivery date.',
})

// A human evaluation. Kept separate from a score, and never averaged with
// one, because the two answer different questions.
mcpd.feedback({
  traceId,
  kind: 'thumbs',
  value: 'up',
  comment: 'Exactly what the customer asked for.',
  authorKind: 'end_user',
})

// Anything you already count can travel with the trace.
mcpd.metric('support.tickets_handled', 1, 'counter', { labels: { queue: 'tier-1' } })

Por HTTP eso es POST /v1/api/scores, con un Score como cuerpo. Una puntuación tiene que nombrar el trace_id o el span_id del que habla; una puntuación enganchada a nada se rechaza con un 400 en vez de guardarse donde nunca se podrá encontrar. Envía score_id como cadena vacía y el servidor genera uno.

De dónde viene una puntuación

source es distinto de scorer_id porque el mismo evaluador se ejecuta en más de un contexto, y los contextos no son comparables. Volver a puntuar el tráfico de la semana pasada con el juez de hoy no es prueba de que la calidad haya mejorado.

OrigenSignificado
liveProducida en línea, mientras ocurría la ejecución trazada.
offlineProducida después, puntuando trazas ya guardadas.
experimentProducida por un experimento ejecutado contra un dataset.
manualIntroducida por una persona, mediante la API o la interfaz.

Una puntuación escrita a través de un SDK toma live por defecto. Para los otros tres valores, ponlo tú.

Puntuar cuesta dinero, así que queda registrado

Una evaluación con juez suele ser otra llamada a un modelo. Puntuar cada ejecución de producción con un juez grande puede costar más de lo que costó servir el tráfico, y esa es una factura que la gente descubre tarde. Por eso un Score lleva un cost con la misma forma que el de un span: una moneda, el reparto entre entrada y salida, y el libro de precios que produjo la cifra. Ponlo cuando escribas la puntuación por la API HTTP o en un lote de ingesta nativo.

score_trace_id es la otra mitad de la misma idea. Traza la propia ejecución de puntuación y su identificador queda en la puntuación, así que un juez que empezó a bajarlo todo se puede abrir, leer y demostrar que está roto.

Feedback humano

El feedback se escribe desde tu propio producto. Llámalo desde el control que la persona pulsó de verdad y el juicio aterriza en la traza exacta que produjo la respuesta a la que estaba reaccionando. kind decide la forma de value.

Tipovalue
thumbsup o down.
ratingUn número, en una escala que declara quien lo produce.
correctionLa respuesta correcta, como texto.
tagUna etiqueta elegida de un conjunto.
commentTexto libre, sin puntuación asociada.

un pulgar abajo desde tu producto

// No traceId: it attaches to the active span's trace.
mcpd.feedback({
  kind: 'thumbs',
  value: 'down',
  comment: 'Named the wrong depot.',
  authorKind: 'end_user',
})

La ocultación actúa sobre los nombres de las claves, y se aplica a los metadata que adjuntas a un juicio. author_id se guarda tal como lo envías, así que envía un identificador seudónimo en lugar de una dirección de correo. Consulta Ocultación y retención.

La consulta que encuentra respuestas malas

Para esto sirven las puntuaciones. has_score nombra un evaluador y score_below pone el listón, y la lista de trazas devuelve las ejecuciones que ese evaluador dejó por debajo. La comparación es estricta, así que score_below=0.5 excluye una ejecución que puntuó exactamente 0.5.

encuentra las ejecuciones con mala puntuación

// The same client reads. This is the query that finds bad answers: every run
// that a scorer put below the bar.
const suspect = await mcpd.listTraces({
  hasScore: 'answer_relevance',
  scoreBelow: 0.5,
  limit: 10,
})
console.log(`runs scoring under 0.5: ${suspect.traces.length}`)

const recent = await mcpd.listTraces({ limit: 5, sort: 'start_time', order: 'desc' })
for (const trace of recent.traces) {
  const seconds = ((trace.durationNs ?? 0) / 1e9).toFixed(2)
  console.log(
    `${trace.traceId.slice(0, 8)}  ${trace.name.padEnd(16)} ${trace.status.padEnd(6)} ` +
      `${seconds}s  ${trace.spanCount} spans  $${(trace.cost?.totalCost ?? 0).toFixed(6)}`,
  )
}

// One run, with every span in it, ordered so a waterfall renders directly.
const detail = await mcpd.getTrace(traceId, { include: ['scores', 'feedback'] })
for (const span of detail.spans) {
  console.log(`  ${span.kind.padEnd(18)} ${span.name}`)
}

score_below solo significa algo con has_score al lado: un listón sin evaluador nombrado es un listón sobre nada. Los dos son parámetros de GET /v1/api/traces, así que se combinan con cualquier otro filtro de ahí. Las respuestas malas de un modelo, en producción, en el último día, son una sola solicitud.

Cómo volver a leerlas

GET /v1/api/scores lista las evaluaciones automáticas, filtradas por scorer_id, source, trace_id, experiment_id, min_value, max_value y una ventana de tiempo, con paginación por cursor. source acepta live, offline, experiment o manual, que es como lees las puntuaciones del propio juez sin las que escribió una persona. GET /v1/api/feedback toma un trace_id y devuelve los juicios sobre esa ejecución. Listar el feedback de un proyecto sin nombrar una ejecución no está implementado en esta versión, y el endpoint lo dice: sin trace_id rechaza con un 400 que nombra el parámetro. Antes respondía una lista vacía, que se lee como si nadie hubiera juzgado nada.

La lista de puntuaciones no acepta order: va de más reciente a más antigua, y su cursor solo compara en un sentido. Las listas de ejecuciones y de spans sí lo aceptan, así que aquí se rechaza por su nombre en vez de aceptarse y descartarse. Lo mismo vale para los filtros kind, from, to y cursor que varios clientes de SDK te dejan poner en una consulta de feedback. Ninguno se aplicó nunca, y los cuatro ahora se rechazan.

Para leer de una vez una ejecución y todo lo que cuelga de ella, pide los extras por su nombre: GET /v1/api/traces/{trace_id}?include=scores,feedback, o include=all para incluir también los logs.

Qué lleva la lista de trazas, y qué no

Cada traza guardada conserva dos agregados para que la lista no necesite ningún join. Uno es el valor medio por evaluador: una media dentro de answer_relevance, y otra media aparte dentro de groundedness. El otro es un recuento de feedback por tipo: tres pulgares, una corrección. Recuentos, no una media, porque la media de un pulgar arriba y una corrección no es ninguna cantidad.

No hay una puntuación global de calidad, y no la habrá. Una sola cifra que combinara un modelo juez, una expresión regular y el pulgar abajo de un cliente sería un número sin unidad y sin manera de rebatirlo. mcpd te enseña las piezas y te deja decidir a ti.

A continuación