Vai al contenuto
mcpd
nessun daemon
Codice

Punteggi e feedback

Un punteggio è quello che ha pensato una macchina. Un feedback è quello che ha pensato una persona. mcpd li tiene come due segnali e non fa mai la media dell’uno nell’altro.

Due segnali, non un numero solo

Un punteggio è una valutazione automatica: un modello giudice, un’espressione regolare, uno unit test, qualunque cosa sappia produrre un numero su un’esecuzione. Un record di feedback è il giudizio di una persona: un pollice giù nel tuo prodotto, il voto di un revisore, una risposta corretta.

Tutti e due si attaccano a un trace_id o a uno span_id, tutti e due arrivano come segnali a pieno titolo, e tutti e due sono archiviati in una tabella propria. Non vengono mai combinati. Un giudice che dà 0.9 di pertinenza e un cliente che preme pollice giù dicono tutti e due il vero, e il disaccordo fra i due è la cosa più utile che c’è sull’esecuzione. Una media cancellerebbe esattamente quello, e ti lascerebbe un numero su cui nessuno può agire.

  • 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

Un punteggio dichiara la sua scala

min e max fanno parte del punteggio. Senza di loro un valore non è interpretabile: 0.86 può essere un buon risultato su una scala da 0 a 1, uno scarso su una scala da 0 a 5, oppure una distanza in cui più basso è meglio. Sei mesi dopo, con due scorer nello stesso progetto, nessuno sa più dire quale delle tre. La specifica li porta a 0 e 1 per impostazione predefinita, e ogni SDK li richiede sul posto della chiamata, così un valore predefinito non viene mai accettato per sbaglio.

CampoA che cosa serve
scorer_id Quale scorer lo ha prodotto, per esempio answer_relevance. Obbligatorio.
scorer_version Un punteggio si confronta solo con un altro punteggio della stessa versione. Cambia il prompt di un giudice e hai un nuovo scorer, non il seguito di quello vecchio.
value Il numero.
min, max La scala dichiarata su cui sta il numero.
label Un risultato categoriale, quando lo scorer ne produce uno accanto al numero.
reason Il perché. Per un modello giudice è la sua spiegazione, ed è quello che rende un punteggio discutibile invece che magico.
source Live, offline, esperimento o manuale.
score_trace_id La traccia dell’esecuzione che ha prodotto il punteggio, così un punteggio sospetto si apre e si legge come qualunque altra esecuzione.
cost Quanto è costato produrre il punteggio.

Registrarne uno

L’helper dell’SDK accoda un punteggio nello stesso modo in cui accoda uno span, quindi la chiamata non blocca e non può sollevare eccezioni. Senza un traceId si attacca alla traccia dello span attivo.

punteggio e 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' } })

Su HTTP è POST /v1/api/scores, che prende uno Score come corpo. Un punteggio deve nominare il trace_id o lo span_id di cui parla; un punteggio attaccato a niente viene rifiutato con 400 invece di essere archiviato dove non lo troverà mai nessuno. Manda score_id come stringa vuota e il server ne genera uno.

Da dove viene un punteggio

source è separato da scorer_id perché lo stesso scorer gira in più di un contesto, e i contesti non sono confrontabili. Ridare un punteggio al traffico della settimana scorsa con il giudice di oggi non è la prova che la qualità sia migliorata.

OrigineChe cosa vuol dire
liveProdotto online, mentre l’esecuzione tracciata avveniva.
offlineProdotto dopo, dando un punteggio a tracce già archiviate.
experimentProdotto da un esperimento eseguito su un dataset.
manualInserito da una persona, tramite l’API o l’interfaccia.

Un punteggio scritto attraverso un SDK vale live se non dici altro. Per gli altri tre lo imposti tu.

Dare un punteggio costa, quindi viene registrato

Una valutazione giudicata di solito è un’altra chiamata al modello. Dare un punteggio a ogni esecuzione di produzione con un giudice grande può costare più di quanto sia costato servire il traffico, ed è una fattura che le persone scoprono tardi. Uno Score porta quindi un cost nella stessa forma in cui lo porta uno span, con una valuta, una divisione fra input e output, e il listino che ha prodotto la cifra. Impostalo quando scrivi il punteggio tramite l’API HTTP o un batch di ingest nativo.

score_trace_id è l’altra metà della stessa idea. Traccia l’esecuzione che produce il punteggio e l’id finisce sul punteggio, così un giudice che ha cominciato a bocciare tutto si può aprire, leggere e mostrare rotto.

Il giudizio delle persone

Il feedback si scrive dal tuo prodotto. Chiamalo dal controllo che la persona ha davvero premuto e il giudizio atterra esattamente sulla traccia che ha prodotto la risposta a cui stava reagendo. kind decide la forma di value.

Tipovalue
thumbsup oppure down.
ratingUn numero, su una scala che dichiara chi lo produce.
correctionLa risposta giusta, come testo.
tagUn’etichetta scelta da un insieme.
commentProsa libera, senza nessun punteggio attaccato.

un pollice giù dal tuo prodotto

// Nessun traceId: si attacca alla traccia dello span attivo.
mcpd.feedback({
  kind: 'thumbs',
  value: 'down',
  comment: 'Named the wrong depot.',
  authorKind: 'end_user',
})

L’oscuramento cerca corrispondenze sui nomi delle chiavi, e passa sopra i metadata che attacchi a un giudizio. author_id viene archiviato come lo mandi, quindi manda uno pseudonimo invece di un indirizzo email. Vedi Oscuramento e conservazione.

La query che trova le risposte sbagliate

È a questo che servono i punteggi. has_score nomina uno scorer e score_below fissa l’asticella, e l’elenco delle tracce restituisce le esecuzioni che quello scorer ha messo sotto. Il confronto è stretto, quindi score_below=0.5 esclude un’esecuzione che ha preso esattamente 0.5.

trovare le esecuzioni con un punteggio basso

// 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 vuol dire qualcosa solo con has_score accanto: un’asticella senza nessuno scorer nominato è un’asticella su niente. Sono tutti e due parametri di GET /v1/api/traces, quindi si combinano con ogni altro filtro che c’è lì. Le risposte sbagliate di un solo modello, in produzione, nell’ultimo giorno, sono una richiesta sola.

Rileggerli

GET /v1/api/scores elenca le valutazioni automatiche, filtrate per scorer_id, source, trace_id, experiment_id, min_value, max_value e una finestra temporale, con paginazione a cursore. source prende live, offline, experiment o manual, ed è così che rileggi i punteggi del giudice senza quelli che ha digitato una persona. GET /v1/api/feedback prende un trace_id e restituisce i giudizi su quell’esecuzione. Elencare il feedback di un progetto senza nominare un’esecuzione non è implementato in questa build, e l’endpoint lo dice: senza trace_id rifiuta con un 400 che nomina il parametro. Prima rispondeva con un elenco vuoto, che si legge come se nessuno avesse giudicato niente.

L’elenco dei punteggi non prende nessun order: è dal più recente, e il suo cursore confronta in un verso solo. Gli elenchi delle esecuzioni e degli span invece uno lo prendono, quindi qui viene rifiutato per nome invece che accettato e buttato via. Lo stesso vale per i filtri kind, from, to e cursor che diversi client SDK ti lasciano impostare su una query di feedback. Nessuno dei quattro è mai stato applicato, e adesso tutti e quattro vengono rifiutati.

Per leggere un’esecuzione e tutto quello che le è attaccato in una volta sola, chiedi gli extra per nome: GET /v1/api/traces/{trace_id}?include=scores,feedback, oppure include=all per avere anche i log.

Che cosa porta l’elenco delle tracce, e che cosa no

Ogni traccia archiviata tiene due aggregati, così l’elenco non ha bisogno di nessuna join. Uno è il valore medio per scorer: una media dentro answer_relevance, e una media separata dentro groundedness. L’altro è un conteggio del feedback per tipo: tre pollici, una correzione. Conteggi, non una media, perché la media di un pollice su e di una correzione non è una quantità.

Non c’è nessun punteggio complessivo di qualità, e non ci sarà. Una cifra sola che mette insieme un modello giudice, un’espressione regolare e il pollice giù di un cliente sarebbe un numero senza unità e senza nessun modo di discuterci. mcpd ti mostra le parti e ti lascia decidere.

Poi