Files
ragflow/internal/ingestion/component/knowledge_compiler/wiki/wiki_test.go
Zhichang Yu 90f46b0b4d Go port: doc-level metadata extraction and knowledge compiler (#17536)
Ports doc-level auto-metadata extraction to Go and adds the
knowledge_compiler component with scheduler/routing. Fixes Extractor
metadata injection type assertion and enable_metadata default-on.
2026-07-29 21:06:48 +08:00

186 lines
5.6 KiB
Go

package wiki
import (
"context"
"strings"
"testing"
"ragflow/internal/ingestion/component/knowledge_compiler/common"
)
func TestReduceExtracts_MergesProvenance(t *testing.T) {
reduced := reduceExtracts([]wikiExtract{
{
Entities: []wikiEntity{{Name: "Alpha", Type: "thing", SourceChunkIDs: []string{"c1"}}},
Claims: []wikiClaim{{Statement: "Alpha exists", Subject: "Alpha", SourceChunkIDs: []string{"c1"}}},
},
{
Entities: []wikiEntity{{Name: "Alpha", Type: "thing", SourceChunkIDs: []string{"c2"}}},
Claims: []wikiClaim{{Statement: "Alpha exists", Subject: "Alpha", SourceChunkIDs: []string{"c2"}}},
},
})
if len(reduced.Entities) != 1 {
t.Fatalf("entities=%d, want 1", len(reduced.Entities))
}
if ids := reduced.Entities[0].SourceChunkIDs; len(ids) != 2 {
t.Fatalf("entity provenance = %#v, want 2 chunk ids", ids)
}
if len(reduced.Claims) != 2 {
t.Fatalf("claims=%d, want 2", len(reduced.Claims))
}
}
func TestPackWikiPlanBatches_SplitsLargeInput(t *testing.T) {
reduced := wikiExtract{
Entities: []wikiEntity{
{Name: strings.Repeat("a", 1000)},
{Name: strings.Repeat("b", 1000)},
{Name: strings.Repeat("c", 1000)},
},
}
batches := packWikiPlanBatches(reduced, 1)
if len(batches) < 2 {
t.Fatalf("expected multiple batches, got %d", len(batches))
}
}
func TestBuildSourceContext_SelectsKnownChunks(t *testing.T) {
ctx := buildSourceContext([]common.Chunk{
{ID: "c1", Text: "alpha text"},
{ID: "c2", Text: "beta text"},
{ID: "c3", Text: "gamma text"},
}, []string{"c2", "c3"})
if strings.Contains(ctx, "alpha text") {
t.Fatalf("source context leaked unselected chunk: %q", ctx)
}
if !strings.Contains(ctx, "beta text") || !strings.Contains(ctx, "gamma text") {
t.Fatalf("source context missing selected chunks: %q", ctx)
}
}
func TestNormalizeWikiPlanPages_FallbacksToEntitiesAndConcepts(t *testing.T) {
plan := normalizeWikiPlan(wikiPlan{}, "doc-1", wikiExtract{
Entities: []wikiEntity{{Name: "Alpha", Aliases: []string{"A"}}},
Concepts: []wikiConcept{{Term: "Beta"}},
})
if len(plan.Pages) < 2 {
t.Fatalf("normalizeWikiPlan generated %d pages, want at least 2", len(plan.Pages))
}
if plan.Pages[0].Slug == "" || plan.Pages[1].Slug == "" {
t.Fatalf("fallback pages missing slugs: %#v", plan.Pages)
}
}
type reconcileChatStub struct {
resp string
}
func (s reconcileChatStub) Chat(_ context.Context, req common.ChatRequest) (*common.ChatResponse, error) {
return &common.ChatResponse{Content: s.resp}, nil
}
type reconcileEmbedStub struct{}
func (reconcileEmbedStub) Encode(_ context.Context, texts []string) ([][]float32, error) {
out := make([][]float32, len(texts))
for i := range texts {
out[i] = []float32{0.1, 0.2, 0.3}
}
return out, nil
}
func (reconcileEmbedStub) Dimensions() int { return 3 }
type wikiStoreStub struct {
slugHit *common.WikiPageCandidate
similar []common.WikiPageCandidate
}
func (s wikiStoreStub) FindSimilarPages(_ context.Context, _, _ string, _ []float32, _ int) ([]common.WikiPageCandidate, error) {
return s.similar, nil
}
func (s wikiStoreStub) GetPageBySlug(_ context.Context, _, _, slug string) (*common.WikiPageCandidate, error) {
if s.slugHit != nil && s.slugHit.Slug == slug {
return s.slugHit, nil
}
return nil, nil
}
func TestReconcilePlanPage_MaybeUsesLLMDecision(t *testing.T) {
p := &wikiPipeline{
ctx: context.Background(),
tenantID: "t1",
datasetID: "kb1",
llmID: "llm1",
deps: common.Deps{
Chat: reconcileChatStub{resp: `{"action":"UPDATE","slug":"entity/existing","reason":"same entity"}`},
Embed: reconcileEmbedStub{},
WikiPages: wikiStoreStub{similar: []common.WikiPageCandidate{{Slug: "entity/existing", Title: "Existing", Score: 0.81}}},
},
}
got, err := p.reconcilePlanPage(wikiPlanPage{
Slug: "entity/new-alpha",
Title: "Alpha",
PageType: "entity",
Topic: "Alpha",
EntityNames: []string{"Alpha Prime"},
}, []float32{0.1, 0.2, 0.3})
if err != nil {
t.Fatalf("reconcilePlanPage err = %v", err)
}
if got == nil || got.Slug != "entity/existing" {
t.Fatalf("reconcilePlanPage = %#v, want entity/existing", got)
}
}
func TestReconcilePlanPage_LowScoreSkipsLLM(t *testing.T) {
p := &wikiPipeline{
ctx: context.Background(),
tenantID: "t1",
datasetID: "kb1",
llmID: "llm1",
deps: common.Deps{
Chat: reconcileChatStub{resp: `{"action":"UPDATE","slug":"entity/existing","reason":"same entity"}`},
Embed: reconcileEmbedStub{},
WikiPages: wikiStoreStub{similar: []common.WikiPageCandidate{{Slug: "entity/existing", Title: "Existing", Score: 0.6}}},
},
}
got, err := p.reconcilePlanPage(wikiPlanPage{
Slug: "entity/new-alpha",
Title: "Alpha",
PageType: "entity",
Topic: "Alpha",
EntityNames: []string{"Alpha Prime"},
}, []float32{0.1, 0.2, 0.3})
if err != nil {
t.Fatalf("reconcilePlanPage err = %v", err)
}
if got != nil {
t.Fatalf("reconcilePlanPage = %#v, want nil", got)
}
}
func TestMergeWikiPageContent_PreservesShortExistingPage(t *testing.T) {
p := &wikiPipeline{
ctx: context.Background(),
deps: common.Deps{
Chat: reconcileChatStub{resp: "# Alpha\n\nAlpha launched a new process in 2026.\n"},
},
}
merged, err := p.mergeWikiPageContent(
"# Alpha\n\nExisting fact.\n",
"# Alpha\n\nAlpha launched a new process in 2026.\n",
"entity/alpha",
)
if err != nil {
t.Fatalf("mergeWikiPageContent err = %v", err)
}
if !strings.Contains(merged, "Existing fact.") {
t.Fatalf("merged page dropped existing content: %q", merged)
}
if !strings.Contains(merged, "Alpha launched a new process in 2026.") {
t.Fatalf("merged page dropped incoming content: %q", merged)
}
}