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

368 lines
9.6 KiB
Go

// Command baselines runs the per-baseline retrieval comparison:
// dispatch the same query set through each registered adapter,
// compare the per-query hit lists against a shared ground truth,
// and emit a per-adapter NDCG@10 + latency table.
//
// Smoke mode (`--smoke`) skips actual runs and reports which adapters
// are available on the local box — useful from CI to verify wiring
// without paying for the heavy Python baselines.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"math"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
func main() {
repo := flag.String("repo", ".", "indexed corpus path")
queriesPath := flag.String("queries", "bench/baselines/queries.json", "JSON query set")
truthPath := flag.String("groundtruth", "bench/baselines/groundtruth.json", "JSON per-query expected file paths")
against := flag.String("against", "ripgrep,probe,colgrep,grepai,coderankembed,semble", "comma-separated adapter names to run (default: all)")
topK := flag.Int("top-k", 10, "top-K candidates per query (NDCG@10 uses K=10)")
out := flag.String("out", "", "output table path (default stdout)")
jsonOut := flag.String("json", "", "optional JSON metrics output")
format := flag.String("format", "markdown", "markdown | json")
smoke := flag.Bool("smoke", false, "skip runs; only probe availability of each requested adapter")
timeout := flag.Duration("timeout", 5*time.Minute, "per-adapter wall-clock cap")
flag.Parse()
requested, err := resolveAdapters(*against)
if err != nil {
die("%v", err)
}
if *smoke {
smokeReport(requested, *format, *out)
return
}
absRepo, err := filepath.Abs(*repo)
if err != nil {
die("repo path: %v", err)
}
if _, err := os.Stat(absRepo); err != nil {
die("repo path: %v", err)
}
queries, err := loadQueries(*queriesPath)
if err != nil {
die("queries: %v", err)
}
truth, err := loadGroundTruth(*truthPath)
if err != nil {
die("groundtruth: %v", err)
}
rows := make([]adapterRow, 0, len(requested))
for _, a := range requested {
fmt.Fprintf(os.Stderr, "[baselines] %s ... ", a.Name())
avail, why := a.Available()
if !avail {
fmt.Fprintf(os.Stderr, "skipped (%s)\n", why)
rows = append(rows, adapterRow{
Adapter: a.Name(),
Skipped: why,
})
continue
}
t0 := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
results := a.Run(ctx, queries, absRepo, *topK)
cancel()
elapsed := time.Since(t0)
row := adapterRow{
Adapter: a.Name(),
PerQuery: results,
TotalDuration: elapsed.String(),
}
row.MedianLatencyMs = medianLatency(results)
row.NDCGAt10 = ndcgAt10(results, truth, *topK)
fmt.Fprintf(os.Stderr, "NDCG@10 %.3f · median %.1fms · total %.1fs\n",
row.NDCGAt10, row.MedianLatencyMs, elapsed.Seconds())
rows = append(rows, row)
}
var primary []byte
switch strings.ToLower(*format) {
case "markdown", "md":
primary = []byte(renderMarkdown(rows))
case "json":
primary = mustMarshalJSON(rows)
default:
die("unknown --format %q", *format)
}
if err := writeOutput(*out, primary); err != nil {
die("write output: %v", err)
}
if *jsonOut != "" {
if err := writeOutput(*jsonOut, mustMarshalJSON(rows)); err != nil {
die("write json: %v", err)
}
}
}
// adapterRow is the per-baseline outcome with the columns the
// published table cares about.
type adapterRow struct {
Adapter string `json:"adapter"`
NDCGAt10 float64 `json:"ndcg_at_10"`
MedianLatencyMs float64 `json:"median_latency_ms"`
TotalDuration string `json:"total_duration"`
PerQuery []queryResult `json:"per_query,omitempty"`
Skipped string `json:"skipped,omitempty"`
}
// resolveAdapters expands the --against flag into the requested
// adapter set, preserving the caller's order. Unknown names error
// out so a typo doesn't silently drop a baseline.
func resolveAdapters(spec string) ([]adapter, error) {
if strings.TrimSpace(spec) == "" {
return allAdapters(), nil
}
var out []adapter
for tok := range strings.SplitSeq(spec, ",") {
name := strings.TrimSpace(tok)
if name == "" {
continue
}
a := adapterByName(name)
if a == nil {
return nil, fmt.Errorf("unknown adapter %q (known: %s)",
name, strings.Join(adapterNames(), ", "))
}
out = append(out, a)
}
if len(out) == 0 {
return allAdapters(), nil
}
return out, nil
}
// smokeReport prints one row per requested adapter showing
// available / not-available with the install hint. Output respects
// --format; default markdown.
func smokeReport(adapters []adapter, format, out string) {
if strings.ToLower(format) == "json" {
rows := make([]map[string]any, 0, len(adapters))
for _, a := range adapters {
avail, why := a.Available()
row := map[string]any{
"adapter": a.Name(),
"available": avail,
}
if why != "" {
row["why"] = why
}
rows = append(rows, row)
}
raw, _ := json.MarshalIndent(rows, "", " ")
_ = writeOutput(out, append(raw, '\n'))
return
}
var b strings.Builder
fmt.Fprintln(&b, "# Baseline-adapter smoke check")
fmt.Fprintln(&b)
fmt.Fprintln(&b, "| adapter | available | reason |")
fmt.Fprintln(&b, "|---------|:---------:|--------|")
for _, a := range adapters {
avail, why := a.Available()
mark := "✗"
if avail {
mark = "✓"
}
if why == "" {
why = "—"
}
fmt.Fprintf(&b, "| %s | %s | %s |\n", a.Name(), mark, why)
}
_ = writeOutput(out, []byte(b.String()))
}
// ndcgAt10 computes NDCG@10 across the result set. Per query: gain
// is 1 for ground-truth hits, 0 otherwise; DCG = sum gain_i /
// log2(i+2); IDCG = DCG of the perfect ordering. Mean across
// queries; 0 when the result set is empty.
func ndcgAt10(results []queryResult, truth map[string][]string, k int) float64 {
if len(results) == 0 {
return 0
}
var sum float64
counted := 0
for _, r := range results {
if r.Error != "" {
continue
}
expected := truth[r.Query]
if len(expected) == 0 {
continue
}
expSet := map[string]bool{}
for _, e := range expected {
expSet[e] = true
}
// DCG@k
var dcg float64
hits := r.Hits
if len(hits) > k {
hits = hits[:k]
}
for i, h := range hits {
if expSet[h] {
dcg += 1.0 / math.Log2(float64(i+2))
}
}
// IDCG@k: best case is min(len(expected), k) hits at the top.
idealHits := min(len(expected), k)
var idcg float64
for i := range idealHits {
idcg += 1.0 / math.Log2(float64(i+2))
}
if idcg == 0 {
continue
}
sum += dcg / idcg
counted++
}
if counted == 0 {
return 0
}
return sum / float64(counted)
}
func medianLatency(results []queryResult) float64 {
vs := make([]float64, 0, len(results))
for _, r := range results {
if r.Error == "" {
vs = append(vs, r.LatencyMs)
}
}
if len(vs) == 0 {
return 0
}
sort.Float64s(vs)
return vs[len(vs)/2]
}
// --- rendering ------------------------------------------------------
func renderMarkdown(rows []adapterRow) string {
var b strings.Builder
fmt.Fprintln(&b, "# Retrieval-baselines NDCG@10 + speed")
fmt.Fprintln(&b)
fmt.Fprintln(&b, "_NDCG@10 against the ground-truth set + median per-query latency for each baseline. Adapters reporting `skipped` weren't available on this box; see the install hints in `bench/baselines/README.md`._")
fmt.Fprintln(&b)
fmt.Fprintln(&b, "| adapter | NDCG@10 | median latency | total | status |")
fmt.Fprintln(&b, "|---------|--------:|---------------:|------:|--------|")
for _, r := range rows {
if r.Skipped != "" {
fmt.Fprintf(&b, "| %s | — | — | — | skipped: %s |\n", r.Adapter, r.Skipped)
continue
}
fmt.Fprintf(&b, "| %s | %.3f | %s | %s | ✓ |\n",
r.Adapter, r.NDCGAt10, fmtMs(r.MedianLatencyMs), r.TotalDuration)
}
fmt.Fprintln(&b)
fmt.Fprintln(&b, summaryLine(rows))
return b.String()
}
func summaryLine(rows []adapterRow) string {
ran := 0
bestName := ""
var bestScore float64
for _, r := range rows {
if r.Skipped != "" {
continue
}
ran++
if r.NDCGAt10 > bestScore {
bestScore = r.NDCGAt10
bestName = r.Adapter
}
}
if ran == 0 {
return "_no adapters available — install at least one baseline (see bench/baselines/README.md)_"
}
return fmt.Sprintf("**Summary:** %d/%d adapter(s) ran. Best NDCG@10: %s @ %.3f.",
ran, len(rows), bestName, bestScore)
}
func fmtMs(v float64) string {
if v == 0 {
return "—"
}
if v < 1 {
return fmt.Sprintf("%.2fms", v)
}
if v < 1000 {
return fmt.Sprintf("%.1fms", v)
}
return fmt.Sprintf("%.2fs", v/1000.0)
}
// --- helpers --------------------------------------------------------
func die(format string, args ...any) {
fmt.Fprintf(os.Stderr, "baselines: "+format+"\n", args...)
os.Exit(1)
}
func writeOutput(path string, body []byte) error {
if path == "" {
_, err := os.Stdout.Write(body)
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.WriteFile(path, body, 0o644)
}
func mustMarshalJSON(rows []adapterRow) []byte {
b, err := json.MarshalIndent(rows, "", " ")
if err != nil {
die("marshal json: %v", err)
}
return append(b, '\n')
}
func loadQueries(path string) ([]string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var doc struct {
Queries []string `json:"queries"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
return doc.Queries, nil
}
func loadGroundTruth(path string) (map[string][]string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var doc struct {
Queries map[string][]string `json:"queries"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err)
}
if doc.Queries == nil {
doc.Queries = map[string][]string{}
}
return doc.Queries, nil
}