192 lines
5.6 KiB
Go
192 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// Legacy-baseline runner for slice 22: run the actual router cascade (stage 0
|
|
// grammars → hash-embedder nearest-centroid classifier → 0.55 confidence gate)
|
|
// over the frozen residual non-action dev pool and project each decision into
|
|
// the five-way non-action semantic space.
|
|
//
|
|
// Projection rules (the daemon's behaviour, not just ScoreLegacy's):
|
|
// - route error → uncertain
|
|
// - Clarify=true (stage 3) → uncertain: the daemon asks, it does not commit
|
|
// to a semantic bucket
|
|
// - chat/query/fact+note/system → conversation/knowledge/memory_write/system
|
|
// - act/reminder on a trusted non-action row → class "action" recorded
|
|
// VERBATIM with illegal_action_prediction=true; never mapped to uncertain
|
|
// - anything else → uncertain
|
|
//
|
|
// Reads /tmp/mvn-s22/pool.json (emit step) and writes /tmp/mvn-s22/legacy.json
|
|
// with both the raw decision fields and the projected class, plus a summary
|
|
// printout. No embedding is recomputed and no label is changed.
|
|
|
|
type poolRow struct {
|
|
IDX int `json:"idx"`
|
|
Text string `json:"text"`
|
|
NText string `json:"n_text"`
|
|
Route string `json:"route"`
|
|
Tags []string `json:"tags"`
|
|
CVFold int `json:"cv_fold"`
|
|
SplitGroup string `json:"split_group"`
|
|
FamilyID string `json:"family_id"`
|
|
SourceID string `json:"source_id"`
|
|
}
|
|
|
|
type legacyRow struct {
|
|
IDX int `json:"idx"`
|
|
Text string `json:"text"`
|
|
Route string `json:"route"`
|
|
Intent string `json:"intent"`
|
|
Class string `json:"class"`
|
|
Illegal bool `json:"illegal_action_prediction"`
|
|
Confidence float64 `json:"confidence"`
|
|
Stage int `json:"stage"`
|
|
Clarify bool `json:"clarify"`
|
|
Producer string `json:"producer"`
|
|
Error string `json:"error,omitempty"`
|
|
SourceID string `json:"source_id"`
|
|
}
|
|
|
|
func main() {
|
|
var mode, poolPath, outPath string
|
|
flag.StringVar(&mode, "mode", "legacy", "baseline mode: legacy (hash classifier) or heads (ONNX cascade minus LLM)")
|
|
flag.StringVar(&poolPath, "pool", "/tmp/mvn-s22/pool.json", "emit-step pool.json")
|
|
flag.StringVar(&outPath, "out", "/tmp/mvn-s22/legacy.json", "output path")
|
|
flag.Parse()
|
|
switch mode {
|
|
case "legacy":
|
|
legacyMain(poolPath, outPath)
|
|
case "heads":
|
|
headsMain(poolPath, outPath)
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown -mode %q\n", mode)
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func legacyMain(poolPath, outPath string) {
|
|
runOverPool(buildMinimalRouter(), poolPath, outPath)
|
|
}
|
|
|
|
func runOverPool(r *router.Router, poolPath, outPath string) {
|
|
raw, err := os.ReadFile(poolPath)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
var rows []poolRow
|
|
if err := json.Unmarshal(raw, &rows); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
now := time.Now()
|
|
|
|
out := make([]legacyRow, 0, len(rows))
|
|
classCount := map[string]int{}
|
|
for _, pr := range rows {
|
|
d, err := r.Route(ctx, router.NormalizedInput{Text: pr.Text}, now)
|
|
lr := legacyRow{
|
|
IDX: pr.IDX,
|
|
Text: pr.Text,
|
|
Route: pr.Route,
|
|
SourceID: pr.SourceID,
|
|
}
|
|
if err != nil {
|
|
lr.Class = "uncertain"
|
|
lr.Error = err.Error()
|
|
} else {
|
|
lr.Intent = string(d.Intent)
|
|
lr.Confidence = d.Confidence
|
|
lr.Stage = d.Stage
|
|
lr.Clarify = d.Clarify
|
|
lr.Producer = string(d.Producer)
|
|
}
|
|
lr.Class, lr.Illegal = project(d, err)
|
|
classCount[lr.Class]++
|
|
out = append(out, lr)
|
|
}
|
|
|
|
if err := writeJSON(outPath, out); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("legacy baseline over %d residual non-action rows:\n", len(out))
|
|
for _, c := range []string{"conversation", "knowledge", "memory_write", "system", "uncertain", "action"} {
|
|
fmt.Printf(" %-14s %d (%.1f%%)\n", c, classCount[c], 100*float64(classCount[c])/float64(len(out)))
|
|
}
|
|
fmt.Printf(" illegal_action_prediction: %d\n", classCount["action"])
|
|
// Grammar hits inside a corpus-residual population would be a
|
|
// corpus/harness disagreement worth telling the report about: the corpus
|
|
// marked each row not-fast-path-resolved, so a current stage-0 rule
|
|
// resolving it means the corpus's fast-path mirror is stale or a grammar
|
|
// landed after the corpus froze.
|
|
gh := 0
|
|
ghByRoute := map[string]int{}
|
|
ghByIntent := map[string]int{}
|
|
for _, lr := range out {
|
|
if lr.Producer == string(router.RouteProducerGrammar) {
|
|
gh++
|
|
ghByRoute[lr.Route]++
|
|
ghByIntent[lr.Intent]++
|
|
}
|
|
}
|
|
fmt.Printf(" stage-0 grammar hits: %d\n", gh)
|
|
if gh > 0 {
|
|
fmt.Printf(" by ground-truth route: %v\n", ghByRoute)
|
|
fmt.Printf(" by grammar intent: %v\n", ghByIntent)
|
|
}
|
|
}
|
|
|
|
// project maps the router's authoritative output into the five-way non-action
|
|
// space, or to the "action" bucket verbatim when the router calls an act or a
|
|
// reminder on a non-action row.
|
|
func project(d router.Decision, err error) (string, bool) {
|
|
if err != nil {
|
|
return "uncertain", false
|
|
}
|
|
if d.Clarify {
|
|
return "uncertain", false
|
|
}
|
|
switch d.Intent {
|
|
case router.IntentChat:
|
|
return "conversation", false
|
|
case router.IntentQuery:
|
|
return "knowledge", false
|
|
case router.IntentFact, router.IntentNote:
|
|
return "memory_write", false
|
|
case router.IntentSystem:
|
|
return "system", false
|
|
case router.IntentAct, router.IntentReminder:
|
|
return "action", true
|
|
default:
|
|
return "uncertain", false
|
|
}
|
|
}
|
|
|
|
func writeJSON(path string, v any) error {
|
|
fh, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fh.Close()
|
|
w := bufio.NewWriter(fh)
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(v); err != nil {
|
|
return err
|
|
}
|
|
return w.Flush()
|
|
} |