package app import ( "context" "errors" "strings" "testing" "github.com/geo-platform/tenant-api/internal/shared/llm" ) func TestBuildGenerationKnowledgeQueryIncludesContextAndFallbackQuestions(t *testing.T) { query := buildGenerationKnowledgeQuery("top_x_article", "Top X 推荐文章", map[string]interface{}{ "topic": "合肥全屋定制", "brand_name": "安徽海翔家居", "region": "合肥", "primary_keyword": "合肥定制家具", "supplemental_questions": []string{"合肥家具品牌怎么选", "全屋定制避坑"}, "key_points": "突出门店与服务能力", }) for _, expected := range []string{ "文章类型:推荐榜文章", "模板名称:Top X 推荐文章", "主题:合肥全屋定制", "品牌:安徽海翔家居", "地域:合肥", "主关键词:合肥定制家具", "兜底检索问题:", } { if !strings.Contains(query, expected) { t.Fatalf("buildGenerationKnowledgeQuery() = %q, want %q", query, expected) } } questions := normalizeKnowledgeResolveQueries(query) if len(questions) != maxKnowledgeQueryQuestions { t.Fatalf("normalizeKnowledgeResolveQueries() len = %d, want %d: %#v", len(questions), maxKnowledgeQueryQuestions, questions) } for _, question := range questions { if !strings.HasSuffix(question, "?") { t.Fatalf("question = %q, want Chinese question mark", question) } } } func TestParseKnowledgeQueryRewriteOutputAcceptsObjectAndArray(t *testing.T) { objectQuestions := parseKnowledgeQueryRewriteOutput(`{"questions":["安徽海翔家居在合肥有哪些门店和联系方式?","安徽海翔家居主营哪些全屋定制产品和服务?","合肥全屋定制用户选择时关注哪些案例和售后?"]}`) if len(objectQuestions) != maxKnowledgeQueryQuestions { t.Fatalf("object questions len = %d, want %d: %#v", len(objectQuestions), maxKnowledgeQueryQuestions, objectQuestions) } if objectQuestions[0] != "安徽海翔家居在合肥有哪些门店和联系方式?" { t.Fatalf("first object question = %q", objectQuestions[0]) } arrayQuestions := parseKnowledgeQueryRewriteOutput(`["安徽海翔家居有什么品牌优势?","合肥全屋定制有哪些案例可引用?","全屋定制选型需要哪些避坑信息?"]`) if len(arrayQuestions) != maxKnowledgeQueryQuestions { t.Fatalf("array questions len = %d, want %d: %#v", len(arrayQuestions), maxKnowledgeQueryQuestions, arrayQuestions) } } func TestNormalizeKnowledgeResolveQueriesPrefersFallbackQuestionBlock(t *testing.T) { query := strings.Join([]string{ "文章类型:推荐榜文章", "主题:合肥全屋定制", "品牌:安徽海翔家居", "重点要求:", "请突出本地服务", "不要写成硬广", "兜底检索问题:", "- 安徽海翔家居在合肥有哪些门店和联系方式?", "- 安徽海翔家居主营哪些全屋定制产品和服务?", "- 合肥全屋定制用户选择时关注哪些案例和售后?", }, "\n") got := normalizeKnowledgeResolveQueries(query) want := []string{ "安徽海翔家居在合肥有哪些门店和联系方式?", "安徽海翔家居主营哪些全屋定制产品和服务?", "合肥全屋定制用户选择时关注哪些案例和售后?", } if len(got) != len(want) { t.Fatalf("normalizeKnowledgeResolveQueries() = %#v, want %#v", got, want) } for index := range want { if got[index] != want[index] { t.Fatalf("normalizeKnowledgeResolveQueries()[%d] = %q, want %q", index, got[index], want[index]) } } } func TestRewriteKnowledgeResolveQueriesUsesKnowledgeURLModelAndJSONObject(t *testing.T) { client := &fakeKnowledgeQueryLLM{ content: `{"questions":["安徽海翔家居在合肥有哪些门店和联系方式?","安徽海翔家居主营哪些全屋定制产品和服务?","合肥全屋定制用户选择时关注哪些案例和售后?"]}`, } svc := &KnowledgeService{ llmClient: client, cfg: knowledgeRuntimeConfig{URLMarkdownModel: "knowledge-url-fast"}, } questions, err := svc.rewriteKnowledgeResolveQueries(context.Background(), "品牌:安徽海翔家居\n地域:合肥") if err != nil { t.Fatalf("rewriteKnowledgeResolveQueries() error = %v", err) } if len(questions) != maxKnowledgeQueryQuestions { t.Fatalf("questions len = %d, want %d: %#v", len(questions), maxKnowledgeQueryQuestions, questions) } if client.request.Model != "knowledge-url-fast" { t.Fatalf("Generate model = %q, want knowledge-url-fast", client.request.Model) } if client.request.ResponseFormat == nil || client.request.ResponseFormat.Type != llm.ResponseFormatTypeJSONObject { t.Fatalf("ResponseFormat = %#v, want json_object", client.request.ResponseFormat) } if !strings.Contains(client.request.Prompt, "品牌:安徽海翔家居") || !strings.Contains(client.request.Prompt, "必须输出 3 个问题") { t.Fatalf("Prompt = %q, want raw user input and three-question instruction", client.request.Prompt) } } type fakeKnowledgeQueryLLM struct { request llm.GenerateRequest content string err error } func (f *fakeKnowledgeQueryLLM) Validate() error { if f.err != nil { return f.err } return nil } func (f *fakeKnowledgeQueryLLM) Generate(_ context.Context, req llm.GenerateRequest, _ func(string)) (*llm.GenerateResult, error) { f.request = req if f.err != nil { return nil, f.err } if strings.TrimSpace(f.content) == "" { return nil, errors.New("empty fake content") } return &llm.GenerateResult{Content: f.content, Model: req.Model}, nil }