本文へスキップ
mcpd
デーモンなし
ソース

スコアとフィードバック

スコアは機械が考えたことです。フィードバックは人が考えたことです。mcpd は両者を別のシグナルとして保ち、一方を他方に平均で溶かし込むことはありません。

1 つの数値ではなく、2 つのシグナル

スコアは機械による評価です。ジャッジモデル、正規表現、 ユニットテストなど、実行について数値を出せるものすべてが含まれます。 フィードバックのレコードは人の判断です。あなたの製品で押された 低評価、レビュアーの採点、訂正された回答などです。

どちらも trace_id または span_id に紐づき、 どちらもそれ自体が 1 つのシグナルとして届き、どちらも専用のテーブルに保存されます。 両者が合成されることはありません。関連性に 0.9 を付けたジャッジと、 低評価を押した利用者は、どちらも正しいことを述べています。そして両者の食い違いこそ、 その実行について最も役に立つ情報です。平均を取れば、まさにその食い違いが消え、 誰も手を打てない数値だけが残ります。

  • 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

スコアは自分の尺度を宣言します

min と max はスコアの一部です。これがなければ値は解釈できません。 0.86 は 0 から 1 の尺度なら良い結果、0 から 5 の尺度なら悪い結果、 低いほど良い距離ならまた別の意味になります。半年後、同じプロジェクトにスコアラーが 2 つあれば、どちらの尺度なのか誰にも分かりません。仕様は既定値を 0 と 1 としていますが、 既定値が事故で受け入れられることのないよう、どの SDK も呼び出し側での指定を必須にしています。

フィールド何のためのものか
scorer_id これを出したスコアラー。たとえば answer_relevance。必須です。
scorer_version スコアを比べられる相手は、同じバージョンから出たスコアだけです。ジャッジのプロンプトを変えたなら、それは古いものの続きではなく新しいスコアラーです。
value 数値そのもの。
min、max その数値が乗る、宣言された尺度。
label スコアラーが数値と併せてカテゴリ的な結果も出す場合の、その結果。
reason 理由。ジャッジモデルならその説明であり、スコアを魔法ではなく議論できるものにしている部分です。
source live、offline、experiment、manual のいずれか。
score_trace_id スコアリングの実行そのもののトレース。疑わしいスコアも、他の実行と同じように開いて読めます。
cost そのスコアを出すのにかかった費用。

スコアを 1 つ記録する

SDK のヘルパーはスパンと同じやり方でスコアをキューに入れます。そのため呼び出しは ブロックせず、例外も投げません。traceId を渡さなければ、 アクティブなスパンのトレースに紐づきます。

スコアとフィードバック

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

HTTP では POST /v1/api/scores にあたり、本体として Score を受け取ります。スコアは対象とする trace_id か span_id を必ず指定しなければなりません。 何にも紐づかないスコアは、二度と見つからない場所に保存する代わりに 400 で拒否されます。 score_id を空文字列で送れば、サーバーが生成します。

スコアがどこから来たか

source が scorer_id と別になっているのは、 同じスコアラーが複数の文脈で動き、その文脈どうしは比べられないからです。 先週のトラフィックを今日のジャッジで採点し直しても、品質が上がった証拠にはなりません。

出所意味
liveトレースされた実行が起きたその場で、オンラインで出されたもの。
offline保存済みのトレースを後から採点して出されたもの。
experimentデータセットに対する実験の実行で出されたもの。
manualAPI または画面から人が入力したもの。

SDK 経由で書かれたスコアの既定値は live です。 残りの 3 つは自分で設定してください。

スコアリングには費用がかかるので、記録します

ジャッジによる評価は、たいていもう一度モデルを呼ぶことです。本番の実行すべてを 大きなジャッジで採点すると、トラフィックを処理するより高くつくことがあり、 その請求は気づくのが遅れがちです。そのため Score は、 スパンと同じ形の cost を持ちます。通貨、入力と出力の内訳、 そしてその金額を出した価格表が入ります。HTTP API またはネイティブの取り込み バッチでスコアを書くときに設定してください。

score_trace_id は同じ考えのもう半分です。スコアリングの実行そのものを トレースすれば、その id がスコアに載ります。何もかも低く付け始めたジャッジを開いて読み、 壊れていることを示せます。

人によるフィードバック

フィードバックはあなたの製品から書き込みます。その人が実際に押したコントロールから 呼び出せば、反応のもとになった回答を出したまさにそのトレースに判断が届きます。 kind が value の形を決めます。

種別value
thumbsup または down。
rating数値。尺度は書き込む側が宣言します。
correction正しい回答を、テキストで。
tag決められた集合から選んだラベル。
commentスコアを伴わない自由記述。

あなたの製品からの低評価

// traceId なし。アクティブなスパンのトレースに紐づきます。
mcpd.feedback({
  kind: 'thumbs',
  value: 'down',
  comment: 'Named the wrong depot.',
  authorKind: 'end_user',
})

秘匿化はキー名で照合され、判断に添えた metadata にも適用されます。 author_id は送られたまま保存されるので、メールアドレスではなく 仮名の id を送ってください。 マスキングと保持を参照してください。

悪い回答を見つけるクエリ

スコアはこのためにあります。has_score でスコアラーを指定し、 score_below で基準を決めると、トレース一覧はそのスコアラーが 基準より下に付けた実行を返します。比較は厳密なので、 score_below=0.5 はちょうど 0.5 だった実行を除きます。

スコアの低かった実行を探す

// 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 が意味を持つのは has_score が隣にあるときだけです。 スコアラーを指定しない基準は、何に対する基準でもありません。どちらも GET /v1/api/traces のパラメーターなので、そこにある他のすべての フィルターと組み合わせられます。特定のモデルによる、本番の、直近 1 日の悪い回答は、 リクエスト 1 つです。

読み出す

GET /v1/api/scores は機械評価を一覧します。scorer_id、 source、trace_id、experiment_id、 min_value、max_value と時間範囲で絞り込め、 カーソルでページを送れます。source は live、 offline、experiment、manual を取ります。 これが、人の手で入力されたものを除いてジャッジ自身のスコアだけを読み出す方法です。 GET /v1/api/feedback は trace_id を取り、 その実行に対する判断を返します。実行を指定せずにプロジェクトのフィードバックを 一覧することは、このビルドでは実装されていません。エンドポイントもそう答えます。 trace_id がなければ、そのパラメーター名を挙げて 400 で拒否します。 以前は空のリストを返しており、それでは誰も何も判断していないように読めていました。

スコア一覧は order を取りません。新しい順に固定で、そのカーソルは 一方向にしか比較しません。実行一覧とスパン一覧は取るので、ここでは黙って受け取って 捨てるのではなく、名前を挙げて拒否します。同じことが、いくつかの SDK クライアントが フィードバックのクエリに設定させてしまう kind、from、 to、cursor のフィルターにも当てはまります。 4 つとも一度も適用されたことがなく、現在は 4 つとも拒否されます。

実行とそれに紐づくものを一度に読むには、追加分を名前で指定してください。 GET /v1/api/traces/{trace_id}?include=scores,feedback、 ログも欲しいなら include=all です。

トレース一覧が持つもの、持たないもの

保存された各トレースは、結合が要らないように 2 つのロールアップを持ちます。 1 つはスコアラーごとの平均値です。answer_relevance の中での平均と、 groundedness の中での平均は別のものです。もう 1 つは種別ごとの フィードバックの件数です。thumbs が 3 件、correction が 1 件、というように。 平均ではなく件数なのは、thumbs-up と correction の平均が量として成り立たないからです。

全体的な品質スコアはありませんし、これから作ることもありません。ジャッジモデルと 正規表現と利用者の低評価を 1 つにまとめた数値は、単位を持たず、反論のしようもない数値です。 mcpd は部品を見せ、判断はあなたに任せます。

次に