package main import ( "fmt" "os" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/router/semantic" ) // headsMain runs the deployed cascade minus the resident LLM: stage-0 // grammars → routing heads (fine-tuned e5 copy + softmax, router_heads.onnx, // 0.6 decline threshold) → ONNX-embedder nearest-centroid classifier → // 0.55 confidence gate. This is what a production turn takes when the model // server is out (docs/routing.md: pickLLMRouter degrades to the classifier). // // The classifier is seeded from models/seeds like the daemon's seedClassifier, // embedded with the real multilingual-e5-small model rather than the block // hash, so this is the closest headless reproduction of the authoritative // router output the slice-22 report can run. // // Requires the ONNX model files and a libonnxruntime.so. Pass the library via // the MAVEN_ONNX_LIB environment variable, exactly as the daemon does. func headsMain(poolPath, outPath string) { lib := os.Getenv("MAVEN_ONNX_LIB") if lib == "" { fmt.Fprintln(os.Stderr, "heads mode needs MAVEN_ONNX_LIB pointing at libonnxruntime.so") os.Exit(2) } const ( embedModel = "models/embedder/multilingual-e5-small/model_quantized.onnx" tokPath = "models/embedder/multilingual-e5-small/tokenizer.json" headsModel = "models/embedder/router-heads/router_heads.onnx" ) emb, err := router.NewONNXEmbedder(embedModel, tokPath, lib) if err != nil { fmt.Fprintf(os.Stderr, "heads: embedder: %v\n", err) os.Exit(1) } defer emb.Close() cls := router.NewClassifier(emb) seedClassifier(cls) heads, err := router.NewRouterHeads(headsModel, tokPath) if err != nil { fmt.Fprintf(os.Stderr, "heads: %v\n", err) os.Exit(1) } defer heads.Close() acts := router.DefaultActMatcher{Fns: semantic.ExperimentActVerbs()} r := router.New(router.Config{ Grammars: router.StageZeroGrammars(acts), Classifier: cls, Extractor: router.Extractor{ Time: router.StubDateTimeParser{}, Acts: acts, Facts: router.DefaultFactParser{}, }, Threshold: 0.55, Heads: heads, }) runOverPool(r, poolPath, outPath) }