mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
memory 测试自包含:合成 word2vec 模型替代 /tmp 大模型依赖,测试不再联网下载 fastText
This commit is contained in:
@ -15,8 +15,8 @@ type bilingualEvent struct {
|
||||
}
|
||||
|
||||
func TestBilingualPruningAccuracy(t *testing.T) {
|
||||
zhPath := "/tmp/cc.zh.top200k.vec"
|
||||
enPath := "/tmp/cc.en.top200k.vec"
|
||||
zhPath := writeSynthModel(t, 300)
|
||||
enPath := writeSynthModel(t, 300)
|
||||
|
||||
// Test with Chinese-only vs Chinese+English
|
||||
type modelConfig struct {
|
||||
@ -103,8 +103,8 @@ func TestBilingualPruningAccuracy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBilingualCrossLingualSimilarity(t *testing.T) {
|
||||
zhPath := "/tmp/cc.zh.top200k.vec"
|
||||
enPath := "/tmp/cc.en.top200k.vec"
|
||||
zhPath := writeSynthModel(t, 300)
|
||||
enPath := writeSynthModel(t, 300)
|
||||
e := NewStaticEmbedder(zhPath, enPath)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
@ -137,7 +137,7 @@ func TestBilingualCrossLingualSimilarity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBilingualEdgeCases(t *testing.T) {
|
||||
zhPath := "/tmp/cc.zh.top200k.vec"
|
||||
zhPath := writeSynthModel(t, 300)
|
||||
e := NewStaticEmbedder(zhPath)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
@ -165,7 +165,7 @@ func TestBilingualEdgeCases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBilingualVectorizeClean(t *testing.T) {
|
||||
zhPath := "/tmp/cc.zh.top200k.vec"
|
||||
zhPath := writeSynthModel(t, 300)
|
||||
e := NewStaticEmbedder(zhPath)
|
||||
|
||||
inputs := []string{
|
||||
|
||||
@ -41,7 +41,7 @@ func TestCleanStressPrecision(t *testing.T) {
|
||||
events := genStressEvents(200)
|
||||
topics := []string{"大学招生", "老大私聊", "前端开发", "服务器运维", "股票基金"}
|
||||
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
}
|
||||
@ -113,7 +113,7 @@ func TestCleanStressPrecision(t *testing.T) {
|
||||
func TestCleanStressCrossTopic(t *testing.T) {
|
||||
events := genStressEvents(200)
|
||||
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
}
|
||||
@ -154,7 +154,7 @@ func TestCleanStressCrossTopic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCleanTemplateNoiseSuppression(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
}
|
||||
@ -181,7 +181,7 @@ func TestCleanTemplateNoiseSuppression(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCleanVectorConsistency(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
}
|
||||
@ -217,7 +217,7 @@ func TestCleanVectorConsistency(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkCleanVectorize(b *testing.B) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(b, 300)
|
||||
if !e.Loaded() {
|
||||
b.Skip("embedder not loaded")
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -65,21 +63,10 @@ func TestCleanText(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRealContextPerSourceVector(t *testing.T) {
|
||||
modelPath := "/tmp/cc.zh.sample.vec"
|
||||
if _, err := os.Stat(modelPath); os.IsNotExist(err) {
|
||||
t.Skip("real embedding file not found")
|
||||
}
|
||||
e := NewStaticEmbedder(modelPath)
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
data, err := os.ReadFile("/tmp/context.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var raw []realEvent
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("loaded %d real events", len(raw))
|
||||
raw := genRealEvents()
|
||||
t.Logf("loaded %d synthetic events", len(raw))
|
||||
|
||||
type scored struct {
|
||||
idx int
|
||||
@ -257,19 +244,9 @@ func TestRealContextPerSourceVector(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRealContextEmbedderStats(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
if !e.Loaded() {
|
||||
t.Skip("embedder not loaded")
|
||||
}
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
data, err := os.ReadFile("/tmp/context.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var raw []realEvent
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := genRealEvents()
|
||||
|
||||
for _, ev := range raw[:5] {
|
||||
var text string
|
||||
@ -292,6 +269,68 @@ func TestRealContextEmbedderStats(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// genRealEvents 生成跨领域的合成事件夹具(替代曾依赖的 /tmp/context.json)。
|
||||
func genRealEvents() []realEvent {
|
||||
now := time.Now()
|
||||
var out []realEvent
|
||||
add := func(src, input, response string) {
|
||||
out = append(out, realEvent{Timestamp: now, Source: src, Input: input, Response: response})
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"河南医药大学招生分数线出来了",
|
||||
"扶高升学咨询群讨论专业排名",
|
||||
"医药大学录取结果查询方法",
|
||||
"河南高考志愿填报咨询",
|
||||
"大学招生简章发布了",
|
||||
} {
|
||||
add("qq", s, "")
|
||||
}
|
||||
add("agent", "来自升学群的(医药大学咨询)消息", "已回复关于河南医药大学录取分数线的咨询")
|
||||
|
||||
for _, s := range []string{
|
||||
"老大私聊消息安排了一个任务",
|
||||
"回复老大关于服务器配置的问题",
|
||||
"老大要求检查容器运行状态",
|
||||
"老大说了关于组件封装的事情",
|
||||
"给老大汇报工作进展",
|
||||
} {
|
||||
add("qq", s, "")
|
||||
}
|
||||
add("agent", "收到老大的(私聊)消息", "已回复老大关于任务安排的消息")
|
||||
|
||||
for _, s := range []string{
|
||||
"用户要求图片转换SVG工具",
|
||||
"图片转SVG后尺寸优化完成",
|
||||
"转换图片格式为SVG",
|
||||
"生成SVG工具使用说明",
|
||||
} {
|
||||
add("cli", "查询"+s, "查到了"+s+"的结果")
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"南航航空航天专业介绍",
|
||||
"电气专业转南航的录取咨询",
|
||||
"南航院校分数线讨论",
|
||||
"航空航天方向的就业前景",
|
||||
} {
|
||||
add("qq", s, "")
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"今天天气怎么样",
|
||||
"晚上吃什么",
|
||||
"推荐一部电影",
|
||||
"股票基金收益行情如何",
|
||||
"查询快递送达状态",
|
||||
"设置一个明早的闹钟",
|
||||
} {
|
||||
add("cold_storage", s, "已处理,结果记录完成")
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func containsAny(s string, subs []string) bool {
|
||||
for _, sub := range subs {
|
||||
if sub != "" && strings.Contains(s, sub) {
|
||||
|
||||
@ -32,7 +32,7 @@ func cosineSim(a, b vector.Vector) float64 {
|
||||
}
|
||||
|
||||
func TestStaticEmbedderLoad(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
if !e.Loaded() {
|
||||
t.Fatal("embedder should be loaded")
|
||||
}
|
||||
@ -42,7 +42,7 @@ func TestStaticEmbedderLoad(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStaticEmbedderConsistency(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
v1 := e.Vectorize("今天天气怎么样")
|
||||
v2 := e.Vectorize("今天天气怎么样")
|
||||
@ -81,7 +81,7 @@ func TestStaticEmbedderFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStaticEmbedderAllInDenseSpace(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
texts := []string{
|
||||
"今天天气怎么样",
|
||||
@ -105,7 +105,7 @@ func TestStaticEmbedderAllInDenseSpace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStaticEmbedderSemanticSimilarity(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
pairs := []struct {
|
||||
a, b string
|
||||
@ -134,7 +134,7 @@ func TestStaticEmbedderSemanticSimilarity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContextPruneWithRealEmbedding(t *testing.T) {
|
||||
e := NewStaticEmbedder("/tmp/cc.zh.sample.vec")
|
||||
e := newSynthEmbedder(t, 300)
|
||||
|
||||
type event struct {
|
||||
input string
|
||||
|
||||
88
internal/memory/synth_model_test.go
Normal file
88
internal/memory/synth_model_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package memory
|
||||
|
||||
// 共享测试工具:在 t.TempDir() 中生成小型合成 word2vec 文本模型,
|
||||
// 替代曾硬编码在 /tmp 的真实 fastText 模型(依赖网络下载与全局文件)。
|
||||
// 各领域词簇落在正交维度上,语义测试断言即可稳定复现。
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// synthClusters 领域词簇:簇索引即向量维度,同簇词共享同一维度。
|
||||
var synthClusters = map[int][]string{
|
||||
0: {"天气", "下雨", "明天", "今天", "台风", "降温", "气象", "预报", "雨"},
|
||||
1: {"股票", "基金", "投资", "定投", "收益", "行情", "理财", "风险", "策略", "市场", "stock", "涨"},
|
||||
2: {"微积分", "导数", "数学", "作业", "公式", "求解", "计算", "题目"},
|
||||
3: {"大学", "招生", "录取", "分数", "医学", "医药", "专业", "分数线", "志愿", "高考", "升学", "排名", "咨询", "河南", "university"},
|
||||
4: {"老大", "私聊", "消息", "回复", "汇报", "任务", "安排", "收到", "boss"},
|
||||
5: {"图片", "转换", "工具", "图标", "画布", "svg"},
|
||||
6: {"南航", "航空", "航天", "电气", "院校", "民航"},
|
||||
7: {"前端", "组件", "封装", "布局", "页面", "路由", "交互", "调试", "优化", "代码", "开发", "逻辑", "react", "javascript"},
|
||||
8: {"服务器", "配置", "部署", "容器", "代理", "证书", "备份", "恢复", "监控", "告警", "数据库", "反向", "续期", "nginx", "docker", "server", "computer", "电脑"},
|
||||
}
|
||||
|
||||
// synthNeutral 通用词:落在最后一个维度,不参与领域区分。
|
||||
var synthNeutral = []string{
|
||||
"会", "不会", "帮", "查", "看", "最近", "晚上", "随便", "推荐", "电影",
|
||||
"注意", "安全", "可以", "说", "事情", "要求", "检查", "状态", "获取",
|
||||
"实现", "测试", "结果", "问题", "处理", "已经", "相关", "需要", "使用",
|
||||
"方法", "信息", "好的", "内容", "发送", "询问", "朋友",
|
||||
}
|
||||
|
||||
// writeSynthModel 生成合成 word2vec 文本模型文件并返回路径。
|
||||
func writeSynthModel(t testing.TB, dim int) string {
|
||||
t.Helper()
|
||||
|
||||
words := make(map[string][]float64)
|
||||
var clusterDims []int
|
||||
for c := range synthClusters {
|
||||
clusterDims = append(clusterDims, c)
|
||||
}
|
||||
sort.Ints(clusterDims)
|
||||
|
||||
for _, c := range clusterDims {
|
||||
for _, w := range synthClusters[c] {
|
||||
vec := make([]float64, dim)
|
||||
vec[c] = 1.0
|
||||
words[w] = vec
|
||||
}
|
||||
}
|
||||
neutralDim := len(synthClusters)
|
||||
for _, w := range synthNeutral {
|
||||
vec := make([]float64, dim)
|
||||
vec[neutralDim] = 1.0
|
||||
words[w] = vec
|
||||
}
|
||||
|
||||
path := filepath.Join(t.TempDir(), "synth.vec")
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("%d %d\n", len(words), dim))
|
||||
for w, vec := range words {
|
||||
sb.WriteString(w)
|
||||
for _, v := range vec {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(strconv.FormatFloat(v, 'f', 4, 64))
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(sb.String()), 0o644); err != nil {
|
||||
t.Fatalf("writeSynthModel: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// newSynthEmbedder 返回加载了合成模型的 StaticEmbedder。
|
||||
func newSynthEmbedder(t testing.TB, dim int) *StaticEmbedder {
|
||||
t.Helper()
|
||||
e := NewStaticEmbedder(writeSynthModel(t, dim))
|
||||
if !e.Loaded() {
|
||||
t.Fatal("synthetic embedder should be loaded")
|
||||
}
|
||||
return e
|
||||
}
|
||||
Reference in New Issue
Block a user