Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / cmd/verify-vector-model/main.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | cmd/verify-vector-model/main.go |
| Original SHA-256 | cc35e02a32cf26b866483fbe246ee625fc624e03fb7468837680aadfa722d8aa |
| Git blob SHA | 770c2c1c6f12917945df839401c9823761b589cc |
| Reading-copy SHA-256 | cc35e02a32cf26b866483fbe246ee625fc624e03fb7468837680aadfa722d8aa |
Recovered source transcript
Download complete source text ↓155 lines · 5.5 KB
// Command verify-vector-model runs the brain's boot vector model gate against a target database, out of
// band, so an operator can confirm the deployed binary's gate will PASS before the cutover swaps the
// binary. It calls the exact same ai.VerifyVectorModel the API runs at boot, with the exact same
// inference client config (base URL, embed model, dimension), so its verdict is the binary's verdict.
//
// It is read only: it samples stored skill vectors and re embeds their names through the plane, then
// compares. It writes nothing and touches no PII. On a PASS the brain will serve; on a mismatch it prints
// the offending skill and cosine, exactly what the 503 vector_model_mismatch would report.
//
// Usage:
//
// verify-vector-model -dsn <app or migrator DSN> -megamind http://host:8090 -model qllama/bge-small-en-v1.5 -dim 384
//
// The gate is role independent (the skills table is global and the dimension checks read the catalog),
// so either the app or the migrator DSN yields the identical verdict.
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"math"
"os"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/pgvector/pgvector-go"
"github.com/missionworkforce/missionmatch/internal/modules/ai"
"github.com/missionworkforce/missionmatch/internal/platform/database"
"github.com/missionworkforce/missionmatch/internal/platform/embeddings"
"github.com/missionworkforce/missionmatch/internal/platform/inference"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
func main() {
dsn := flag.String("dsn", "", "target database DSN, defaults to MM_MIGRATOR_DATABASE_URL")
// NOT an env derived default, because flag usage prints defaults and a gateway URL can carry credentials.
mega := flag.String("megamind", "", "MEGAMIND gateway base URL, defaults to MM_MEGAMIND_URL")
model := flag.String("model", envDefault("MM_MEGAMIND_EMBED_MODEL", "qllama/bge-small-en-v1.5"), "configured embed model")
dim := flag.Int("dim", 384, "expected embedding width")
flag.Parse()
if *dsn == "" {
*dsn = os.Getenv("MM_MIGRATOR_DATABASE_URL")
}
if *mega == "" {
*mega = envDefault("MM_MEGAMIND_URL", "http://100.94.8.94:8090")
}
if *dsn == "" || *mega == "" {
log.Fatal("verify-vector-model: -dsn and -megamind are required")
}
ctx := context.Background()
pool, err := database.NewPool(ctx, *dsn)
if err != nil {
log.Fatalf("db: %v", err)
}
defer pool.Close()
db := tenant.New(pool)
client, err := inference.New(inference.Config{
BaseURL: *mega, EmbedModel: *model, Dimension: *dim, Timeout: 60 * time.Second, MaxRetries: 2,
})
if err != nil {
log.Fatalf("inference: %v", err)
}
if err := client.Health(ctx); err != nil {
log.Fatalf("plane not reachable at %s: %v", *mega, err)
}
// Transparency loop: print the cosine for the same sample the gate uses, so a PASS shows margin and a
// FAIL shows exactly how far each sampled skill sits from its fresh embedding. This mirrors the gate's
// SELECT and its SkillEmbedText composition; the authoritative verdict still comes from the gate call.
if err := printSampleCosines(ctx, pool, client); err != nil {
log.Printf("transparency sample failed (continuing to the authoritative gate): %v", err)
}
// The authoritative verdict: the exact function the API binary runs at boot.
switch err := ai.VerifyVectorModel(ctx, db, client, *dim); {
case err == nil:
fmt.Printf("\nVECTOR MODEL GATE: PASS (stored vectors match %s at dim %d). The brain will serve.\n", *model, *dim)
case errors.Is(err, ai.ErrVectorModelMismatch):
fmt.Printf("\nVECTOR MODEL GATE: FAIL (mismatch). The brain would return 503 vector_model_mismatch.\n %v\n", err)
os.Exit(1)
default:
fmt.Printf("\nVECTOR MODEL GATE: INCONCLUSIVE (plane or config error, the gate would retry on first query).\n %v\n", err)
os.Exit(2)
}
}
// printSampleCosines re embeds the same 5 sample skills the gate samples and prints each cosine, so the
// operator sees the numbers behind the verdict.
func printSampleCosines(ctx context.Context, pool *pgxpool.Pool, client *inference.Client) error {
rows, err := pool.Query(ctx, "SELECT name, COALESCE(category,''), embedding FROM skills WHERE embedding IS NOT NULL ORDER BY id LIMIT 5")
if err != nil {
return err
}
defer rows.Close()
var names, texts []string
var stored [][]float32
for rows.Next() {
var name, category string
var v pgvector.Vector
if err := rows.Scan(&name, &category, &v); err != nil {
return err
}
names = append(names, name)
texts = append(texts, embeddings.SkillEmbedText(name, category))
stored = append(stored, v.Slice())
}
if err := rows.Err(); err != nil {
return err
}
if len(texts) == 0 {
fmt.Println("no backfilled skill embeddings to sample")
return nil
}
res, err := client.Embed(ctx, texts)
if err != nil {
return err
}
fmt.Println("sample cosines (stored vs fresh embedding of the same text):")
for i := range stored {
if i >= len(res.Embeddings) {
break
}
fmt.Printf(" %-40q cosine=%.6f\n", texts[i]+" ["+names[i]+"]", cosineSim(stored[i], res.Embeddings[i]))
}
return nil
}
func envDefault(k, def string) string {
if v := os.Getenv(k); v != "" {
return v
}
return def
}
// cosineSim is the same true cosine the gate uses (divides by both norms), for the transparency print.
func cosineSim(a, b []float32) float64 {
var dot, na, nb float64
for i := range a {
dot += float64(a[i]) * float64(b[i])
na += float64(a[i]) * float64(a[i])
nb += float64(b[i]) * float64(b[i])
}
if na == 0 || nb == 0 {
return 0
}
return dot / (math.Sqrt(na) * math.Sqrt(nb))
}