277 lines
8.4 KiB
Go
277 lines
8.4 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/shared/retrieval"
|
|
)
|
|
|
|
func TestExtractKnowledgePreciseFactsKeepsAddressAndContactLines(t *testing.T) {
|
|
facts := extractKnowledgePreciseFacts(`
|
|
# 合肥门店
|
|
|
|
地址:合肥市蜀山区望江西路 123 号 A 座 8 层
|
|
电话:0551-12345678
|
|
官网:https://example.com/store
|
|
普通介绍段落
|
|
`)
|
|
|
|
for _, expected := range []string{
|
|
"地址:合肥市蜀山区望江西路 123 号 A 座 8 层",
|
|
"电话:0551-12345678",
|
|
"官网:https://example.com/store",
|
|
} {
|
|
if !containsExactString(facts, expected) {
|
|
t.Fatalf("extractKnowledgePreciseFacts() = %#v, want %q", facts, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderPromptSectionIncludesPreciseFactsAndSourceURI(t *testing.T) {
|
|
sourceURI := "https://example.com/store"
|
|
output := (&KnowledgeService{}).RenderPromptSection(&KnowledgeContext{
|
|
Snippets: []KnowledgeSnippet{
|
|
{
|
|
GroupName: "门店信息",
|
|
ItemName: "合肥门店",
|
|
SourceURI: &sourceURI,
|
|
Text: "这里是一段门店介绍。",
|
|
PreciseFacts: []string{"地址:合肥市蜀山区望江西路 123 号", "电话:0551-12345678"},
|
|
},
|
|
},
|
|
})
|
|
|
|
for _, expected := range []string{
|
|
"以下为必须严格按原文保留的高精度事实",
|
|
"- 门店信息 / 合肥门店:地址:合肥市蜀山区望江西路 123 号",
|
|
"1. [K1] 门店信息 / 合肥门店",
|
|
"来源链接: https://example.com/store",
|
|
"这里是一段门店介绍。",
|
|
} {
|
|
if !strings.Contains(output, expected) {
|
|
t.Fatalf("RenderPromptSection() = %q, want %q", output, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderPromptSectionSupportsPreciseFactsWithoutSnippets(t *testing.T) {
|
|
output := (&KnowledgeService{}).RenderPromptSection(&KnowledgeContext{
|
|
PreciseFacts: []string{
|
|
"公司资料 / 合肥门店:地址:合肥市蜀山区望江西路 123 号",
|
|
"公司资料 / 合肥门店:主营业务:全屋定制;橱柜定制",
|
|
},
|
|
})
|
|
|
|
for _, expected := range []string{
|
|
"以下为必须严格按原文保留的高精度事实",
|
|
"- 公司资料 / 合肥门店:地址:合肥市蜀山区望江西路 123 号",
|
|
"- 公司资料 / 合肥门店:主营业务:全屋定制;橱柜定制",
|
|
} {
|
|
if !strings.Contains(output, expected) {
|
|
t.Fatalf("RenderPromptSection() = %q, want %q", output, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractKnowledgePreciseFactsKeepsBusinessScopeSection(t *testing.T) {
|
|
facts := extractKnowledgePreciseFacts(`
|
|
# 公司介绍
|
|
|
|
主营业务
|
|
- 全屋定制
|
|
- 橱柜定制
|
|
- 木门与护墙系统
|
|
|
|
地址:合肥市蜀山区望江西路 123 号
|
|
`)
|
|
|
|
for _, expected := range []string{
|
|
"主营业务:全屋定制;橱柜定制;木门与护墙系统",
|
|
"地址:合肥市蜀山区望江西路 123 号",
|
|
} {
|
|
if !containsExactString(facts, expected) {
|
|
t.Fatalf("extractKnowledgePreciseFacts() = %#v, want %q", facts, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestKnowledgeSnippetsFromSearchPointsSkipsMissingTextAndDedupes(t *testing.T) {
|
|
snippets := dedupeKnowledgeSearchCandidates(knowledgeSnippetsFromSearchPoints([]retrieval.SearchPoint{
|
|
{
|
|
ID: "point-1",
|
|
Score: 0.91,
|
|
Payload: map[string]any{
|
|
"group_id": int64(10),
|
|
"group_name": "产品资料",
|
|
"item_id": int64(100),
|
|
"item_name": "旗舰款",
|
|
"text": " 支持全屋定制方案 ",
|
|
},
|
|
},
|
|
{
|
|
ID: "point-2",
|
|
Payload: map[string]any{
|
|
"group_id": int64(10),
|
|
"group_name": "产品资料",
|
|
"item_id": int64(100),
|
|
"item_name": "旗舰款",
|
|
"text": "支持全屋定制方案",
|
|
},
|
|
},
|
|
{
|
|
ID: "point-3",
|
|
Payload: map[string]any{},
|
|
},
|
|
}))
|
|
|
|
if len(snippets) != 1 {
|
|
t.Fatalf("deduped snippets length = %d, want 1: %#v", len(snippets), snippets)
|
|
}
|
|
if snippets[0].Text != "支持全屋定制方案" {
|
|
t.Fatalf("snippet text = %q, want trimmed text", snippets[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestKnowledgeSearchTextIncludesMetadataForRetrieval(t *testing.T) {
|
|
got := buildKnowledgeSearchText(knowledgeSearchTextInput{
|
|
GroupName: "产品资料",
|
|
ItemName: "旗舰款",
|
|
SourceType: "website",
|
|
SourceURI: "https://example.com/product",
|
|
Text: "支持全屋定制方案",
|
|
})
|
|
|
|
for _, expected := range []string{
|
|
"目录: 产品资料",
|
|
"标题: 旗舰款",
|
|
"来源类型: website",
|
|
"来源链接: https://example.com/product",
|
|
"内容:\n支持全屋定制方案",
|
|
} {
|
|
if !strings.Contains(got, expected) {
|
|
t.Fatalf("buildKnowledgeSearchText() = %q, want %q", got, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestKnowledgeSnippetsFromSearchPointsKeepsSearchTextForRerank(t *testing.T) {
|
|
snippets := knowledgeSnippetsFromSearchPoints([]retrieval.SearchPoint{
|
|
{
|
|
ID: "point-1",
|
|
Score: 0.91,
|
|
Payload: map[string]any{
|
|
"group_id": int64(10),
|
|
"group_name": "产品资料",
|
|
"item_id": int64(100),
|
|
"item_name": "旗舰款",
|
|
"source_type": "website",
|
|
"source_uri": "https://example.com/product",
|
|
"text": "支持全屋定制方案",
|
|
"search_text": "目录: 产品资料\n标题: 旗舰款\n内容:\n支持全屋定制方案",
|
|
},
|
|
},
|
|
})
|
|
|
|
if len(snippets) != 1 {
|
|
t.Fatalf("snippets length = %d, want 1", len(snippets))
|
|
}
|
|
if snippets[0].Text != "支持全屋定制方案" {
|
|
t.Fatalf("Text = %q, want original chunk text", snippets[0].Text)
|
|
}
|
|
if snippets[0].RerankText() != "目录: 产品资料\n标题: 旗舰款\n内容:\n支持全屋定制方案" {
|
|
t.Fatalf("RerankText() = %q, want search text", snippets[0].RerankText())
|
|
}
|
|
if snippets[0].SourceURI == nil || *snippets[0].SourceURI != "https://example.com/product" {
|
|
t.Fatalf("SourceURI = %#v, want payload source uri", snippets[0].SourceURI)
|
|
}
|
|
}
|
|
|
|
func TestDedupeKnowledgeSearchCandidatesUsesMetadataAwareSearchText(t *testing.T) {
|
|
candidates := []KnowledgeSnippet{
|
|
{
|
|
PointID: "p1",
|
|
KnowledgeItemID: 1,
|
|
ItemName: "A 款",
|
|
Text: "支持全屋定制方案",
|
|
SearchText: "标题: A 款\n内容:\n支持全屋定制方案",
|
|
},
|
|
{
|
|
PointID: "p2",
|
|
KnowledgeItemID: 2,
|
|
ItemName: "B 款",
|
|
Text: "支持全屋定制方案",
|
|
SearchText: "标题: B 款\n内容:\n支持全屋定制方案",
|
|
},
|
|
}
|
|
|
|
deduped := dedupeKnowledgeSearchCandidates(candidates)
|
|
if len(deduped) != 2 {
|
|
t.Fatalf("deduped length = %d, want 2: %#v", len(deduped), deduped)
|
|
}
|
|
}
|
|
|
|
func TestSelectKnowledgeRerankedSnippetsFillsAndDiversifiesResults(t *testing.T) {
|
|
candidates := []KnowledgeSnippet{
|
|
{PointID: "p1", KnowledgeItemID: 1, Text: "同一文档片段 1", Score: 0.90},
|
|
{PointID: "p2", KnowledgeItemID: 1, Text: "同一文档片段 2", Score: 0.89},
|
|
{PointID: "p3", KnowledgeItemID: 1, Text: "同一文档片段 3", Score: 0.88},
|
|
{PointID: "p4", KnowledgeItemID: 2, Text: "第二文档片段", Score: 0.80},
|
|
{PointID: "p5", KnowledgeItemID: 3, Text: "第三文档片段", Score: 0.70},
|
|
}
|
|
|
|
selected := selectKnowledgeRerankedSnippets(candidates, []retrieval.RerankResult{
|
|
{Index: 2, RelevanceScore: 0.99},
|
|
{Index: 99, RelevanceScore: 1},
|
|
}, 4)
|
|
|
|
if len(selected) != 4 {
|
|
t.Fatalf("selected length = %d, want 4: %#v", len(selected), selected)
|
|
}
|
|
if selected[0].PointID != "p3" || selected[0].Score != 0.99 {
|
|
t.Fatalf("selected[0] = %#v, want reranked p3 with rerank score", selected[0])
|
|
}
|
|
if selected[1].PointID != "p1" || selected[2].PointID != "p4" || selected[3].PointID != "p5" {
|
|
t.Fatalf("selected order = %#v, want reranked result filled with diverse vector candidates", selected)
|
|
}
|
|
}
|
|
|
|
func TestSelectKnowledgeRerankedSnippetsKeepsOriginalChunkText(t *testing.T) {
|
|
candidates := []KnowledgeSnippet{
|
|
{
|
|
PointID: "p1",
|
|
KnowledgeItemID: 1,
|
|
Text: "原始正文片段",
|
|
SearchText: "目录: 产品资料\n标题: 旗舰款\n内容:\n原始正文片段",
|
|
Score: 0.90,
|
|
},
|
|
}
|
|
|
|
selected := selectKnowledgeRerankedSnippets(candidates, []retrieval.RerankResult{
|
|
{
|
|
Index: 0,
|
|
RelevanceScore: 0.99,
|
|
Text: "目录: 产品资料\n标题: 旗舰款\n内容:\n原始正文片段",
|
|
},
|
|
}, 1)
|
|
|
|
if len(selected) != 1 {
|
|
t.Fatalf("selected length = %d, want 1", len(selected))
|
|
}
|
|
if selected[0].Text != "原始正文片段" {
|
|
t.Fatalf("selected text = %q, want original chunk text", selected[0].Text)
|
|
}
|
|
if selected[0].Score != 0.99 {
|
|
t.Fatalf("selected score = %v, want rerank score", selected[0].Score)
|
|
}
|
|
}
|
|
|
|
func containsExactString(items []string, target string) bool {
|
|
for _, item := range items {
|
|
if item == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|