Vectrum
Sign In
153 lines · 4.4 KB · Go
package main

import (
	"fmt"
	"io"
	"sort"
	"strings"
	"text/tabwriter"
)

// report prints the numbers METRICS.md wants: the headline pass rate, and
// enough per-task and per-tool detail to tell which change caused a move.
func report(w io.Writer, results []Result) {
	if len(results) == 0 {
		return
	}
	fmt.Fprintln(w, "\n"+strings.Repeat("─", 78))

	byTask := map[string][]Result{}
	var order []string
	for _, r := range results {
		if _, seen := byTask[r.Task]; !seen {
			order = append(order, r.Task)
		}
		byTask[r.Task] = append(byTask[r.Task], r)
	}
	sort.Strings(order)

	tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
	fmt.Fprintln(tw, "TASK\tPASS\tTURNS\tIN\tOUT\tCACHED\tWALL")
	for _, name := range order {
		rs := byTask[name]
		fmt.Fprintf(tw, "%s\t%d/%d\t%.1f\t%.0f\t%.0f\t%.0f\t%.1fs\n",
			name, countPass(rs), len(rs),
			mean(rs, func(r Result) float64 { return float64(r.Turns) }),
			mean(rs, func(r Result) float64 { return float64(r.Usage.InputTokens) }),
			mean(rs, func(r Result) float64 { return float64(r.Usage.OutputTokens) }),
			mean(rs, func(r Result) float64 { return float64(r.Usage.CacheReadInputTokens) }),
			mean(rs, func(r Result) float64 { return float64(r.WallMS) / 1000 }))
	}
	tw.Flush()

	// A task counts as solved if every repeat passed; anything less is a task
	// Codey cannot do reliably, which is the number worth reporting.
	var solved int
	for _, name := range order {
		if countPass(byTask[name]) == len(byTask[name]) {
			solved++
		}
	}

	fmt.Fprintln(w, strings.Repeat("─", 78))
	calls, fails := toolStats(results)
	fmt.Fprintf(w, "pass rate      %d/%d tasks solved on every run (%d/%d runs passed)\n",
		solved, len(order), countPass(results), len(results))
	fmt.Fprintf(w, "edit success   %s\n", rate(calls["edit_file"]-fails["edit_file"], calls["edit_file"]))
	fmt.Fprintf(w, "mean turns     %.1f\n", mean(results, func(r Result) float64 { return float64(r.Turns) }))
	fmt.Fprintf(w, "mean tokens    %.0f in (%.0f cached), %.0f out\n",
		mean(results, func(r Result) float64 { return float64(r.Usage.InputTokens) }),
		mean(results, func(r Result) float64 { return float64(r.Usage.CacheReadInputTokens) }),
		mean(results, func(r Result) float64 { return float64(r.Usage.OutputTokens) }))
	fmt.Fprintf(w, "mean cost      %s\n", costCell(results))
	fmt.Fprintf(w, "mean wall      %.1fs\n", mean(results, func(r Result) float64 { return float64(r.WallMS) / 1000 }))

	fmt.Fprintln(w, "\ntool calls (failures in brackets)")
	tw = tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
	var names []string
	for name := range calls {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Fprintf(tw, "  %s\t%d\t[%d]\t%s ok\n", name, calls[name], fails[name],
			rate(calls[name]-fails[name], calls[name]))
	}
	tw.Flush()

	fmt.Fprintln(w, "\nMETRICS.md row:")
	fmt.Fprintf(w, "| <change> | %d/%d | %s | %.1f | %s |\n",
		solved, len(order),
		rate(calls["edit_file"]-fails["edit_file"], calls["edit_file"]),
		mean(results, func(r Result) float64 { return float64(r.Turns) }),
		costCell(results))
}

// cost prices one run. Pricing lives in the llm package so the REPL's running
// total and this table cannot drift apart.
func cost(r Result) float64 {
	c, _ := r.Usage.Cost(r.Model)
	return c
}

// costKnown reports whether every run had priceable usage, so an unpriced
// model produces a blank rather than $0.0000.
func costKnown(results []Result) bool {
	for _, r := range results {
		if _, ok := r.Usage.Cost(r.Model); !ok {
			return false
		}
	}
	return len(results) > 0
}

func costCell(results []Result) string {
	if !costKnown(results) {
		return "n/a"
	}
	return fmt.Sprintf("$%.4f", mean(results, cost))
}

func toolStats(results []Result) (calls, fails map[string]int) {
	calls, fails = map[string]int{}, map[string]int{}
	for _, r := range results {
		for _, ev := range r.Events {
			if ev.Type != "tool_call" {
				continue
			}
			calls[ev.Tool]++
			if ev.OK != nil && !*ev.OK {
				fails[ev.Tool]++
			}
		}
	}
	return calls, fails
}

func countPass(rs []Result) int {
	var n int
	for _, r := range rs {
		if r.Pass {
			n++
		}
	}
	return n
}

func mean(rs []Result, f func(Result) float64) float64 {
	if len(rs) == 0 {
		return 0
	}
	var sum float64
	for _, r := range rs {
		sum += f(r)
	}
	return sum / float64(len(rs))
}

func rate(ok, total int) string {
	if total == 0 {
		return "n/a"
	}
	return fmt.Sprintf("%.0f%%", 100*float64(ok)/float64(total))
}