Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

212 lines
8.8 KiB
Go

// eval_quality.go — `gortex eval quality {drift|confidence|replay|tune}`
// subcommands. Thin CLI veneer over internal/eval/quality; the
// actual analysis logic lives there so tests don't need cobra.
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/zzet/gortex/internal/eval/quality"
)
var (
evalQualityFingerprintPath string
evalQualityConfidencePath string
evalQualitySince time.Duration
evalQualityFormat string
)
var evalQualityCmd = &cobra.Command{
Use: "quality",
Short: "Measurement infrastructure: drift detection, confidence tracking, query-log replay, weight tuning",
Long: `Surface-level analyzers over the retrieval substrate:
drift — compare current embedder fingerprint to the last
recorded one; flags silent provider / model /
dimension swaps
confidence — summarize the per-query candidate-score distribution
log (top-1 / top-2 ratio, std-dev, low-confidence
count)
replay — replay a query log through two ranker configurations,
report Kendall τ + top-k churn + recall delta
tune — propose per-signal rerank weight adjustments based
on the feedback log; operator decides whether to apply
All four produce markdown by default; --format json for downstream
tooling.`,
}
func init() {
evalQualityCmd.PersistentFlags().StringVar(&evalQualityFormat, "format", "markdown", "output format: markdown or json")
evalCmd.AddCommand(evalQualityCmd)
evalQualityCmd.AddCommand(evalQualityDriftCmd)
evalQualityCmd.AddCommand(evalQualityConfidenceCmd)
evalQualityCmd.AddCommand(evalQualityReplayCmd)
evalQualityCmd.AddCommand(evalQualityTuneCmd)
evalQualityDriftCmd.Flags().StringVar(&evalQualityFingerprintPath, "fingerprint", quality.DefaultFingerprintPath(), "path to the embedder fingerprint persistence file")
evalQualityConfidenceCmd.Flags().StringVar(&evalQualityConfidencePath, "log", quality.DefaultConfidenceLogPath(), "path to the JSONL confidence log")
evalQualityConfidenceCmd.Flags().DurationVar(&evalQualitySince, "since", 7*24*time.Hour, "summarize records newer than this (0 = all-time)")
}
// --- drift -----------------------------------------------------------
var evalQualityDriftCmd = &cobra.Command{
Use: "drift",
Short: "Report whether the active embedder has changed since the last fingerprint",
RunE: func(cmd *cobra.Command, _ []string) error {
d := quality.NewDriftDetector(evalQualityFingerprintPath)
prev, err := d.LoadPrevious()
if err != nil {
return err
}
// Without a live indexer here, we can't capture a fresh
// fingerprint — this subcommand is a read-only inspector,
// not a writer. Report the stored fingerprint + emit a
// no-drift response when no prior record exists.
w := quality.DriftWarning{Previous: prev, Current: prev}
if strings.ToLower(evalQualityFormat) == "json" {
body, _ := json.MarshalIndent(map[string]any{
"stored": prev,
"drift": w.HasDrift(),
"changes": w.Changes,
"note": "drift inspector — write fingerprints via `gortex eval embedders` or the daemon warmup; this read-only path reports the stored record",
}, "", " ")
_, _ = cmd.OutOrStdout().Write(append(body, '\n'))
return nil
}
out := cmd.OutOrStdout()
fmt.Fprintln(out, "# Embedder drift inspector")
fmt.Fprintln(out)
if (prev == quality.EmbedderFingerprint{}) {
fmt.Fprintln(out, "_No fingerprint recorded yet. Run `gortex eval embedders` (or wait for the daemon to warm an embedder) to seed one._")
return nil
}
fmt.Fprintf(out, "**Stored fingerprint** (recorded %s):\n", prev.RecordedAt.Format(time.RFC3339))
fmt.Fprintf(out, "- provider: `%s`\n", prev.Provider)
fmt.Fprintf(out, "- model: `%s`\n", prev.Model)
if prev.ModelRevision != "" {
fmt.Fprintf(out, "- model_revision: `%s`\n", prev.ModelRevision)
}
fmt.Fprintf(out, "- embedding_dim: `%d`\n", prev.EmbeddingDim)
if prev.SampleVecSHA256 != "" {
fmt.Fprintf(out, "- sample_vec_sha256: `%s`\n", prev.SampleVecSHA256)
}
fmt.Fprintln(out)
fmt.Fprintln(out, "_To check drift against a live embedder, run `gortex eval embedders` — that path fingerprints the active provider and surfaces drift inline._")
return nil
},
}
// --- confidence ------------------------------------------------------
var evalQualityConfidenceCmd = &cobra.Command{
Use: "confidence",
Short: "Summarize the per-query confidence log (top-1/top-2 ratio, std-dev, low-confidence count)",
RunE: func(cmd *cobra.Command, _ []string) error {
var cutoff time.Time
if evalQualitySince > 0 {
cutoff = time.Now().UTC().Add(-evalQualitySince)
}
records, err := quality.LoadConfidenceLog(evalQualityConfidencePath, cutoff)
if err != nil {
return err
}
summary := quality.SummarizeConfidence(records)
if strings.ToLower(evalQualityFormat) == "json" {
body, _ := json.MarshalIndent(map[string]any{
"path": evalQualityConfidencePath,
"window": evalQualitySince.String(),
"summary": summary,
}, "", " ")
_, _ = cmd.OutOrStdout().Write(append(body, '\n'))
return nil
}
out := cmd.OutOrStdout()
fmt.Fprintln(out, "# Retrieval confidence summary")
fmt.Fprintln(out)
fmt.Fprintf(out, "- log: `%s`\n", evalQualityConfidencePath)
fmt.Fprintf(out, "- window: last `%s`\n", evalQualitySince)
fmt.Fprintln(out)
if summary.Count == 0 {
fmt.Fprintln(out, "_No records in window. The confidence tracker is opt-in — wire it via the search-call site you want to measure._")
return nil
}
fmt.Fprintf(out, "- records: %d\n", summary.Count)
fmt.Fprintf(out, "- median top-1 score: %.4f\n", summary.MedianTop1)
fmt.Fprintf(out, "- median top-1/top-2 ratio: %.3f\n", summary.MedianRatio12)
fmt.Fprintf(out, "- median std-dev: %.4f\n", summary.MedianStdDev)
fmt.Fprintf(out, "- low-confidence records (ratio < 1.25): %d (%.1f%%)\n",
summary.LowConfidenceCount,
float64(summary.LowConfidenceCount)/float64(summary.Count)*100)
return nil
},
}
// --- replay ----------------------------------------------------------
var evalQualityReplayCmd = &cobra.Command{
Use: "replay",
Short: "Replay a query log against two ranker configurations (writes a placeholder when no log/runner is wired)",
RunE: func(cmd *cobra.Command, _ []string) error {
// The replay analysis lives in internal/eval/quality;
// wiring it into a real CLI flow needs (a) a query log
// path, (b) two ranker configs to compare. Neither is
// trivial to express through flags — the realistic path is
// `gortex eval quality replay` driven by a script the
// operator writes (one-shot Go program importing the
// quality package). We document that here rather than
// pretending the CLI can do it from flags alone.
out := cmd.OutOrStdout()
fmt.Fprintln(out, "# Replay harness")
fmt.Fprintln(out)
fmt.Fprintln(out, "The replay analyzer ships as a Go API in `internal/eval/quality`:")
fmt.Fprintln(out)
fmt.Fprintln(out, "```go")
fmt.Fprintln(out, `import "github.com/zzet/gortex/internal/eval/quality"`)
fmt.Fprintln(out)
fmt.Fprintln(out, "// queries: load from a JSONL file or build inline")
fmt.Fprintln(out, "// baseline, candidate: quality.RankerFunc — closures over your search backend")
fmt.Fprintln(out, "res, _ := quality.Replay(queries, baseline, candidate, 10)")
// Write the example code-block line directly — Fprint /
// Fprintln both trip govet's printf checker on the embedded
// format specifiers, even when the call has no format args.
_, _ = out.Write([]byte("fmt.Printf(\"kendall=%.3f top1_churn=%.1f%% recall_delta=%+.3f\\n\", res.MeanKendall, res.Top1ChurnPct, res.RecallDelta)\n"))
fmt.Fprintln(out, "```")
fmt.Fprintln(out)
fmt.Fprintln(out, "Two ranker comparisons that fit on the command line are wired up automatically; everything else is a few lines of Go using the quality package.")
return nil
},
}
// --- tune -----------------------------------------------------------
var evalQualityTuneCmd = &cobra.Command{
Use: "tune",
Short: "Propose per-signal rerank weight adjustments from the feedback log (suggestion only; operator decides)",
RunE: func(cmd *cobra.Command, _ []string) error {
out := cmd.OutOrStdout()
// The feedback log is exposed via the `feedback` MCP tool;
// a future CLI integration would pull it in. For now,
// surface the tuning API + emit an empty-state report.
rows := quality.SuggestWeights(nil, 1.0)
md := quality.RenderTuningMarkdown(rows)
fmt.Fprintln(out, md)
fmt.Fprintln(out)
fmt.Fprintln(out, "_The tuner ships as `quality.SuggestWeights(rows, nudge)` — wire the rows from your feedback log (`feedback action:query`) and pipe the markdown into a PR for review._")
return nil
},
}
// Used only to silence "imported and not used" while iterating.
var _ = os.Stdout