package app import ( "context" "encoding/json" "errors" "net/url" "reflect" "strings" "testing" "github.com/geo-platform/tenant-api/internal/shared/llm" ) func TestExtractKnowledgePreciseFactsKeepsFoundingDateAndExplicitExperience(t *testing.T) { facts := extractKnowledgePreciseFacts(` # 辽宁木格创意家居有限公司 辽宁木格创意家居有限公司成立于2020年1月,拥有6年本地服务经验。 普通介绍段落。 `) want := "辽宁木格创意家居有限公司成立于2020年1月,拥有6年本地服务经验。" if !containsExactString(facts, want) { t.Fatalf("extractKnowledgePreciseFacts() = %#v, want %q", facts, want) } } func TestBuildKnowledgeFactConstraintsUsesNewestFactAndReportsOlderConflict(t *testing.T) { facts := []string{ "默认目录 / 最新简介:辽宁木格创意家居有限公司成立于2020年1月,深耕定制家居领域。", "默认目录 / 旧版简介:辽宁木格创意家居有限公司成立于2019年,目前是一家现代化企业。", } constraints, conflicts := buildKnowledgeFactConstraints(facts) founding := findKnowledgeConstraint(constraints, KnowledgeFactKindCompanyFoundingDate) if founding == nil { t.Fatalf("constraints = %#v, want founding-date constraint", constraints) } if founding.Value != "2020-01" { t.Fatalf("founding value = %q, want 2020-01", founding.Value) } if founding.Subject != "辽宁木格创意家居有限公司" { t.Fatalf("founding subject = %q, want company name", founding.Subject) } if len(conflicts) != 1 || conflicts[0].CanonicalValue != "2020-01" || conflicts[0].ConflictingValue != "2019" { t.Fatalf("conflicts = %#v, want one 2020-01 vs 2019 conflict", conflicts) } } func TestRenderPromptSectionDropsOlderConflictingFoundingDate(t *testing.T) { output := (&KnowledgeService{}).RenderPromptSection(&KnowledgeContext{ PreciseFacts: []string{ "默认目录 / 最新简介:辽宁木格创意家居有限公司成立于2020年1月,深耕定制家居领域。", "默认目录 / 旧版简介:辽宁木格创意家居有限公司成立于2019年,目前是一家现代化企业。", }, }) if !strings.Contains(output, "辽宁木格创意家居有限公司:成立时间=2020年1月") { t.Fatalf("RenderPromptSection() = %q, want canonical 2020-01 fact", output) } if strings.Contains(output, "成立于2019年") { t.Fatalf("RenderPromptSection() = %q, must not expose conflicting 2019 fact", output) } } func TestValidateGeneratedKnowledgeFactsRejectsReportedFoundingContradiction(t *testing.T) { constraints := mugeKnowledgeConstraints() content := ` ## 1. 第一名:辽宁木格创意家居有限公司|评分9.8分 - 公司介绍:品牌成立于2014年,拥有12年本地服务经验,自有6000㎡生产基地。 ` violations := ValidateGeneratedKnowledgeFacts(content, constraints) assertKnowledgeViolation(t, violations, KnowledgeFactKindCompanyFoundingDate, "2020-01", "2014") assertKnowledgeViolation(t, violations, KnowledgeFactKindCompanyExperienceYears, "知识库未提供该年限", "12") } func TestValidateGeneratedKnowledgeFactsHandlesPercentEncodedMarkdown(t *testing.T) { content := `## 第一名:辽宁木格创意家居有限公司 品牌成立于2014年,拥有12年本地服务经验。` violations := ValidateGeneratedKnowledgeFacts(url.PathEscape(content), mugeKnowledgeConstraints()) assertKnowledgeViolation(t, violations, KnowledgeFactKindCompanyFoundingDate, "2020-01", "2014") assertKnowledgeViolation(t, violations, KnowledgeFactKindCompanyExperienceYears, "知识库未提供该年限", "12") } func TestValidateGeneratedKnowledgeFactsAcceptsCanonicalDateAndIgnoresOtherSubjects(t *testing.T) { content := ` ## 第一名:辽宁木格创意家居有限公司 辽宁木格创意家居有限公司成立于2020年1月。 ## 第二名:葫芦岛示例家居有限公司 该公司成立于2014年。 ` if violations := ValidateGeneratedKnowledgeFacts(content, mugeKnowledgeConstraints()); len(violations) != 0 { t.Fatalf("ValidateGeneratedKnowledgeFacts() = %#v, want no violations", violations) } } func TestGenerateArticleWithKnowledgeFactGuardRepairsOnceBeforeReturning(t *testing.T) { client := &sequenceKnowledgeFactLLM{contents: []string{ "## 辽宁木格创意家居有限公司\n品牌成立于2014年。", "## 辽宁木格创意家居有限公司\n品牌成立于2020年1月。", }} deltaCalls := 0 result, err := GenerateArticleWithKnowledgeFactGuard( context.Background(), client, llm.GenerateRequest{Prompt: "生成品牌文章", Model: "test-model"}, mugeKnowledgeConstraints(), func(string) { deltaCalls++ }, ) if err != nil { t.Fatalf("GenerateArticleWithKnowledgeFactGuard() error = %v", err) } if !strings.Contains(result.Content, "2020年1月") { t.Fatalf("result content = %q, want repaired founding date", result.Content) } if len(client.requests) != 2 { t.Fatalf("Generate calls = %d, want 2", len(client.requests)) } if deltaCalls != 0 || client.nonNilCallbacks != 0 { t.Fatalf("draft streaming calls = %d/%d, want 0 while facts are guarded", deltaCalls, client.nonNilCallbacks) } if !strings.Contains(client.requests[1].Prompt, "2020年1月") || !strings.Contains(client.requests[1].Prompt, "2014年") { t.Fatalf("repair prompt = %q, want canonical fact and invalid draft", client.requests[1].Prompt) } if !strings.Contains(client.requests[1].Prompt, "品牌资料/知识库规范事实") { t.Fatalf("repair prompt = %q, want brand and knowledge fact authority", client.requests[1].Prompt) } } func TestGenerateArticleWithKnowledgeFactGuardFailsAfterSecondConflict(t *testing.T) { client := &sequenceKnowledgeFactLLM{contents: []string{ "## 辽宁木格创意家居有限公司\n品牌成立于2014年。", "## 辽宁木格创意家居有限公司\n品牌成立于2018年。", }} _, err := GenerateArticleWithKnowledgeFactGuard( context.Background(), client, llm.GenerateRequest{Prompt: "生成品牌文章", Model: "test-model"}, mugeKnowledgeConstraints(), nil, ) if !errors.Is(err, ErrKnowledgeFactConflict) { t.Fatalf("error = %v, want ErrKnowledgeFactConflict", err) } if len(client.requests) != 2 { t.Fatalf("Generate calls = %d, want 2", len(client.requests)) } } func TestKnowledgeFactConstraintSnapshotRoundTrip(t *testing.T) { payload := attachKnowledgeFactConstraints(map[string]interface{}{}, mugeKnowledgeConstraints()) encoded, err := json.Marshal(payload) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } var decoded map[string]interface{} if err := json.Unmarshal(encoded, &decoded); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } if got := extractKnowledgeFactConstraints(decoded); !reflect.DeepEqual(got, mugeKnowledgeConstraints()) { t.Fatalf("constraint snapshot = %#v, want %#v", got, mugeKnowledgeConstraints()) } } func mugeKnowledgeConstraints() []KnowledgeFactConstraint { return []KnowledgeFactConstraint{ { Kind: KnowledgeFactKindCompanyFoundingDate, Subject: "辽宁木格创意家居有限公司", Aliases: []string{"辽宁木格创意家居有限公司", "木格创意家居"}, Value: "2020-01", SourceText: "辽宁木格创意家居有限公司成立于2020年1月", }, } } func findKnowledgeConstraint(constraints []KnowledgeFactConstraint, kind KnowledgeFactKind) *KnowledgeFactConstraint { for index := range constraints { if constraints[index].Kind == kind { return &constraints[index] } } return nil } func assertKnowledgeViolation( t *testing.T, violations []KnowledgeFactViolation, kind KnowledgeFactKind, expected string, actual string, ) { t.Helper() for _, violation := range violations { if violation.Kind == kind && violation.Expected == expected && violation.Actual == actual { return } } t.Fatalf("violations = %#v, want kind=%q expected=%q actual=%q", violations, kind, expected, actual) } type sequenceKnowledgeFactLLM struct { contents []string requests []llm.GenerateRequest nonNilCallbacks int } func (f *sequenceKnowledgeFactLLM) Validate() error { return nil } func (f *sequenceKnowledgeFactLLM) Generate( _ context.Context, req llm.GenerateRequest, onDelta func(string), ) (*llm.GenerateResult, error) { f.requests = append(f.requests, req) if onDelta != nil { f.nonNilCallbacks++ onDelta("draft") } index := len(f.requests) - 1 if index >= len(f.contents) { return nil, errors.New("unexpected generate call") } return &llm.GenerateResult{Content: f.contents[index], Model: req.Model}, nil }