mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-02 13:57:30 +08:00
## Summary Refactor the Go `KnowledgeCompilerComponent` so its parameter is a **single string template id** instead of a DSL-level `variant` (or plural group id list). The `variant` is no longer in the DSL — it is now **derived at runtime from the resolved compilation template's `kind` field**. This aligns the Go ingestion port with the frontend Compiler operator, which emits a singular `compilation_template_group_id` and does not write `variant` into the generated `compiler.json`.
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package tree
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// cosEmbeddings places points on the unit circle so that the cosine similarity
|
|
// between consecutive points equals sims[i]. It lets tests assert exact
|
|
// watershed cut points from a known similarity profile.
|
|
func cosEmbeddings(sims []float64) [][]float64 {
|
|
angles := make([]float64, len(sims)+1)
|
|
for i, c := range sims {
|
|
if c > 1 {
|
|
c = 1
|
|
}
|
|
if c < -1 {
|
|
c = -1
|
|
}
|
|
angles[i+1] = angles[i] + math.Acos(c)
|
|
}
|
|
emb := make([][]float64, len(angles))
|
|
for i, a := range angles {
|
|
emb[i] = []float64{math.Cos(a), math.Sin(a)}
|
|
}
|
|
return emb
|
|
}
|
|
|
|
func uniqueCount(labels []int) int {
|
|
seen := map[int]struct{}{}
|
|
for _, l := range labels {
|
|
seen[l] = struct{}{}
|
|
}
|
|
return len(seen)
|
|
}
|
|
|
|
func TestWatershedEdgeCases(t *testing.T) {
|
|
if _, err := watershed(nil, 4); err == nil {
|
|
t.Fatalf("expected error for empty input")
|
|
}
|
|
labels, err := watershed([][]float64{{1, 0}}, 4)
|
|
if err != nil || len(labels) != 1 || labels[0] != 0 {
|
|
t.Fatalf("n=1: got %v err %v", labels, err)
|
|
}
|
|
// n=2: the Python reference always splits two points into two clusters.
|
|
labels, err = watershed([][]float64{{1, 0}, {0, 1}}, 4)
|
|
if err != nil || len(labels) != 2 || labels[0] != 0 || labels[1] != 1 {
|
|
t.Fatalf("n=2: got %v err %v", labels, err)
|
|
}
|
|
}
|
|
|
|
// TestWatershedTreeOrderControlsCount verifies that the tree_order branching
|
|
// factor drives the expected cluster count: for a monotone similarity drop the
|
|
// number of clusters is non-increasing as the order grows (larger order => fewer,
|
|
// bigger clusters). The expected count is approximately 1 + (N-1)/order.
|
|
func TestWatershedTreeOrderControlsCount(t *testing.T) {
|
|
emb := cosEmbeddings([]float64{0.2, 0.4, 0.6, 0.8}) // N = 5 points
|
|
cases := []struct {
|
|
order int
|
|
want int
|
|
}{
|
|
{2, 3},
|
|
{3, 2},
|
|
{4, 2},
|
|
{10, 2},
|
|
}
|
|
prev := 1 << 30
|
|
for _, c := range cases {
|
|
labels, err := watershed(emb, c.order)
|
|
if err != nil {
|
|
t.Fatalf("order %v: %v", c.order, err)
|
|
}
|
|
got := uniqueCount(labels)
|
|
if got != c.want {
|
|
t.Fatalf("order %v: got %d clusters, want %d", c.order, got, c.want)
|
|
}
|
|
if got > prev {
|
|
t.Fatalf("order %v: clusters %d > previous %d (not non-increasing)", c.order, got, prev)
|
|
}
|
|
prev = got
|
|
}
|
|
}
|
|
|
|
func TestWatershedDeterministic(t *testing.T) {
|
|
emb := cosEmbeddings([]float64{0.1, 0.9, 0.2, 0.8, 0.3})
|
|
a, err := watershed(emb, 4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := watershed(emb, 4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
t.Fatalf("non-deterministic at %d: %v vs %v", i, a, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWatershedScaleInvariant(t *testing.T) {
|
|
emb := cosEmbeddings([]float64{0.2, 0.4, 0.6, 0.8})
|
|
scaled := make([][]float64, len(emb))
|
|
for i, r := range emb {
|
|
scaled[i] = []float64{r[0] * 5, r[1] * 5}
|
|
}
|
|
a, _ := watershed(emb, 2)
|
|
b, _ := watershed(scaled, 2)
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
t.Fatalf("scale changed labels at %d: %v vs %v", i, a, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestWatershedTreeOrderDefault verifies the exported default is honored when
|
|
// the parameter is absent.
|
|
func TestWatershedTreeOrderDefault(t *testing.T) {
|
|
emb := cosEmbeddings([]float64{0.2, 0.4, 0.6, 0.8})
|
|
a, _ := watershed(emb, DefaultTreeOrder)
|
|
b, _ := watershed(emb, 4)
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
t.Fatalf("default order != 4 at %d: %v vs %v", i, a, b)
|
|
}
|
|
}
|
|
}
|