Ir para o conteúdo
mcpd
sem daemon
Código

Notas e feedback

Uma nota é o que uma máquina achou. Um feedback é o que uma pessoa achou. O mcpd guarda os dois como dois sinais e nunca dilui um na média do outro.

Dois sinais, não um número

Uma nota é uma avaliação automática: um modelo juiz, uma expressão regular, um teste unitário, qualquer coisa capaz de produzir um número sobre uma execução. Um registro de feedback é o julgamento de uma pessoa: um polegar para baixo dentro do seu produto, a avaliação de um revisor, uma resposta corrigida.

Os dois se prendem a um trace_id ou a um span_id, os dois chegam como sinais por direito próprio, e cada um fica na sua própria tabela. Eles nunca são combinados. Um juiz dando 0,9 de relevância e um cliente apertando o polegar para baixo são duas afirmações verdadeiras, e a discordância entre elas é a coisa mais útil da execução. Uma média apagaria justamente isso, e deixaria você com um número sobre o qual ninguém consegue agir.

  • 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

Uma nota declara a sua escala

min e max fazem parte da nota. Sem eles um valor não é interpretável: 0,86 pode ser um bom resultado numa escala de 0 a 1, um resultado ruim numa escala de 0 a 5, ou uma distância em que menor é melhor. Seis meses depois, com dois avaliadores no mesmo projeto, ninguém consegue dizer qual é qual. A especificação usa 0 e 1 como padrão, e todo SDK exige os dois no ponto da chamada, para que um padrão nunca seja aceito por acidente.

CampoPara que serve
scorer_id Qual avaliador produziu esta nota, por exemplo answer_relevance. Obrigatório.
scorer_version Uma nota só se compara com outra nota da mesma versão. Mude o prompt de um juiz e você tem um avaliador novo, não a continuação do antigo.
value O número.
min, max A escala declarada em que o número está.
label Um resultado categórico, quando o avaliador produz um ao lado do número.
reason Por quê. Para um modelo juiz é a explicação dele, e é o que torna uma nota discutível em vez de mágica.
source Live, offline, experiment ou manual.
score_trace_id O trace da própria execução de avaliação, para que uma nota suspeita possa ser aberta e lida como qualquer outra execução.
cost Quanto custou produzir a nota.

Como registrar uma

O auxiliar do SDK enfileira uma nota do mesmo jeito que enfileira um span, então a chamada não bloqueia e não pode lançar exceção. Sem nenhum traceId informado, ela se prende ao trace do span ativo.

nota 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' } })

Por HTTP isso é POST /v1/api/scores, recebendo um Score no corpo. Uma nota precisa nomear o trace_id ou o span_id a que ela se refere; uma nota presa a nada é recusada com 400 em vez de ficar guardada onde nunca será encontrada. Mande score_id como string vazia e o servidor gera um.

De onde veio uma nota

source é separado de scorer_id porque o mesmo avaliador roda em mais de um contexto, e os contextos não são comparáveis. Reavaliar o tráfego da semana passada com o juiz de hoje não é prova de que a qualidade melhorou.

OrigemO que significa
liveProduzida on-line, enquanto a execução rastreada acontecia.
offlineProduzida depois, avaliando traces já guardados.
experimentProduzida por um experimento rodado contra um dataset.
manualDigitada por uma pessoa, pela API ou pela interface.

Uma nota escrita por um SDK vem como live por padrão. Para as outras três, defina você mesmo.

Avaliar custa dinheiro, então isso fica registrado

Uma avaliação por juiz costuma ser mais uma chamada de modelo. Avaliar toda execução de produção com um juiz grande pode custar mais do que custou atender o tráfego, e essa é uma conta que as pessoas descobrem tarde. Por isso um Score carrega um cost no mesmo formato de um span, com moeda, a divisão entre entrada e saída, e a tabela de preços que produziu o número. Defina esse valor quando você escrever a nota pela API HTTP ou por um lote de ingestão nativo.

score_trace_id é a outra metade da mesma ideia. Rastreie a própria execução de avaliação e o id vai parar na nota, então um juiz que começou a rebaixar tudo pode ser aberto, lido e mostrado como defeituoso.

Feedback humano

O feedback é escrito a partir do seu próprio produto. Chame-o a partir do controle que a pessoa realmente apertou e o julgamento cai no trace exato que produziu a resposta a que ela estava reagindo. O kind decide o formato do value.

Tipovalue
thumbsup ou down.
ratingUm número, numa escala que quem produz declara.
correctionA resposta certa, em texto.
tagUm rótulo escolhido de um conjunto.
commentProsa livre, sem nota nenhuma junto.

um polegar para baixo vindo do seu produto

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

A ocultação age sobre nomes de chave, e ele roda sobre os metadata que você prende a um julgamento. O author_id é guardado exatamente como você o manda, então mande um id pseudônimo em vez de um endereço de e-mail. Veja Ocultação e retenção.

A consulta que encontra respostas ruins

É para isto que servem as notas. has_score nomeia um avaliador e score_below define a barra, e a lista de traces devolve as execuções que aquele avaliador colocou abaixo dela. A comparação é estrita, então score_below=0.5 deixa de fora uma execução que tirou exatamente 0.5.

encontre as execuções que foram mal

// 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 só significa alguma coisa com has_score ao lado: uma barra sem avaliador nomeado é uma barra sobre nada. Os dois são parâmetros de GET /v1/api/traces, então se combinam com todos os outros filtros de lá. Respostas ruins de um modelo, em produção, no último dia, é uma requisição só.

Como ler tudo de volta

GET /v1/api/scores lista as avaliações automáticas, filtradas por scorer_id, source, trace_id, experiment_id, min_value, max_value e uma janela de tempo, com paginação por cursor. source aceita live, offline, experiment ou manual, que é como você lê de volta as notas do próprio juiz sem as que uma pessoa digitou. GET /v1/api/feedback recebe um trace_id e devolve os julgamentos daquela execução. Listar o feedback de um projeto sem nomear uma execução não está implementado nesta versão, e o endpoint diz isso: sem trace_id ele recusa com um 400 nomeando o parâmetro. Antes ele respondia uma lista vazia, o que se lê como se ninguém tivesse julgado nada.

A lista de notas não aceita order: ela vem da mais nova para a mais antiga, e o cursor dela compara em um sentido só. As listas de execuções e de spans aceitam, então aqui o parâmetro é recusado pelo nome em vez de ser aceito e descartado. O mesmo vale para os filtros kind, from, to e cursor que vários clientes de SDK deixam você definir numa consulta de feedback. Nenhum deles chegou a ser aplicado, e os quatro agora são recusados.

Para ler uma execução e tudo que está preso a ela de uma vez, peça os extras pelo nome: GET /v1/api/traces/{trace_id}?include=scores,feedback, ou include=all para levar os logs junto.

O que a lista de traces carrega, e o que ela não carrega

Cada trace guardado mantém dois resumos, para que a lista não precise de nenhum join. Um é o valor médio por avaliador: uma média dentro de answer_relevance, e outra média separada dentro de groundedness. O outro é uma contagem de feedback por tipo: três polegares, uma correção. Contagens, e não uma média, porque a média de um polegar para cima com uma correção não é uma quantidade.

Não existe uma nota geral de qualidade, e não vai existir. Um único número somando um modelo juiz, uma expressão regular e o polegar para baixo de um cliente seria um número sem unidade e sem como discutir com ele. O mcpd mostra as partes e deixa a decisão com você.

A seguir