Files
zzet--gortex/internal/search/rerank/query_alpha_continuous_test.go
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

107 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rerank
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
func TestAlphaForContinuous_AnchorsAndBounds(t *testing.T) {
cases := []struct {
query string
want float64
}{
{"FooBar", AlphaSymbol}, // single identifier token
{"validate_token", AlphaSymbol}, // snake_case identifier
{"what does this do", AlphaNL}, // prose, stopword-heavy
{"", AlphaNL}, // empty
{"internal/auth/token.go", AlphaPath}, // path shape
{"func(ctx) error", AlphaSignature}, // signature shape
}
for _, c := range cases {
if got := AlphaForContinuous(c.query); got != c.want {
t.Errorf("AlphaForContinuous(%q) = %v, want %v", c.query, got, c.want)
}
}
// Every result stays within the discrete envelope [AlphaPath, AlphaNL].
for _, q := range []string{"validateToken handler", "auth middleware", "parse the jwt cache token please"} {
got := AlphaForContinuous(q)
if got < AlphaPath || got > AlphaNL {
t.Errorf("AlphaForContinuous(%q) = %v, out of [%v,%v]", q, got, AlphaPath, AlphaNL)
}
}
}
func TestAlphaForContinuous_Monotonic(t *testing.T) {
// More identifier-shaped → lower α (more BM25-leaning).
twoIdents := AlphaForContinuous("validateToken parseJWT")
mixed := AlphaForContinuous("validateToken handler")
prose := AlphaForContinuous("validate the user token")
if !(twoIdents <= mixed && mixed <= prose) {
t.Errorf("expected α to rise with prose-ness: twoIdents=%v mixed=%v prose=%v", twoIdents, mixed, prose)
}
if twoIdents != AlphaSymbol {
t.Errorf("two pure identifiers should pin to AlphaSymbol, got %v", twoIdents)
}
}
func TestContinuousClassMultiplier_AnchorsAndMonotonic(t *testing.T) {
// NL anchor reproduces the neutral 1.0/1.0 baseline exactly.
if got := continuousClassMultiplier(AlphaNL, SignalBM25); got != 1.0 {
t.Errorf("bm25 mult at AlphaNL = %v, want 1.0", got)
}
if got := continuousClassMultiplier(AlphaNL, SignalSemantic); got != 1.0 {
t.Errorf("semantic mult at AlphaNL = %v, want 1.0", got)
}
// Path anchor reproduces the discrete path-class multipliers.
if got := continuousClassMultiplier(AlphaPath, SignalBM25); !floatNear(got, classWeightTable[QueryClassPath].bm25, 1e-9) {
t.Errorf("bm25 mult at AlphaPath = %v, want %v", got, classWeightTable[QueryClassPath].bm25)
}
if got := continuousClassMultiplier(AlphaPath, SignalSemantic); !floatNear(got, classWeightTable[QueryClassPath].semantic, 1e-9) {
t.Errorf("semantic mult at AlphaPath = %v, want %v", got, classWeightTable[QueryClassPath].semantic)
}
// Non-text signals are never class-scaled.
if got := continuousClassMultiplier(AlphaPath, SignalFanIn); got != 1.0 {
t.Errorf("fan_in mult = %v, want 1.0 (only bm25/semantic are class-sensitive)", got)
}
// Monotonic: lowering α raises bm25 and lowers semantic.
if !(continuousClassMultiplier(AlphaSymbol, SignalBM25) > continuousClassMultiplier(AlphaNL, SignalBM25)) {
t.Errorf("lower α must raise bm25 multiplier")
}
if !(continuousClassMultiplier(AlphaSymbol, SignalSemantic) < continuousClassMultiplier(AlphaNL, SignalSemantic)) {
t.Errorf("lower α must lower semantic multiplier")
}
}
func TestRerank_ContinuousAlphaTunesTextVsSemantic(t *testing.T) {
g := newTestGraph()
textOnly := mustNode(g, "f.go::TextOnly", "TextOnly", graph.KindFunction)
vecOnly := mustNode(g, "f.go::VecOnly", "VecOnly", graph.KindFunction)
weights := map[string]float64{SignalBM25: 1.0, SignalSemantic: 1.0}
p := New(DefaultSignals(), weights)
score := func(alpha float64, id string) float64 {
cands := []*Candidate{
{Node: textOnly, TextRank: 0, VectorRank: -1},
{Node: vecOnly, TextRank: -1, VectorRank: 0},
}
p.Rerank("q", cands, &Context{Graph: g, Alpha: alpha})
for _, c := range cands {
if c.Node.ID == id {
return c.Score
}
}
t.Fatalf("candidate %s missing from rerank output", id)
return 0
}
// A BM25-leaning α (path end) lifts the text-only candidate and
// trims the semantic-only one relative to the neutral NL α.
if !(score(AlphaPath, textOnly.ID) > score(AlphaNL, textOnly.ID)) {
t.Errorf("low α must raise the text-only candidate's score")
}
if !(score(AlphaPath, vecOnly.ID) < score(AlphaNL, vecOnly.ID)) {
t.Errorf("low α must lower the semantic-only candidate's score")
}
}