Files
aiagent/backend/tests/test_memory_system.py

422 lines
14 KiB
Python
Raw Normal View History

"""
天工 Agent 记忆系统 全功能测试用例
覆盖P0 分类 / P1 向量化 / P2 Rerank / P3 异步压缩 / P4 Auto Dream
P5 离线兜底 / P6 团队共享 / P7 文件记忆 / 核心嵌入 / 压缩 / 知识池
运行cd backend && python tests/test_memory_system.py
"""
import asyncio
import sys
import time
import tempfile
import shutil
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# ─── 测试框架 ───
PASS = 0
FAIL = 0
SKIP = 0
def test(name: str):
"""装饰器风格的测试标记"""
def decorator(fn):
global PASS, FAIL, SKIP
try:
result = fn()
if asyncio.iscoroutine(result):
result = asyncio.run(result)
if result is False:
FAIL += 1
print(f" FAIL {name}")
elif result is True:
PASS += 1
print(f" PASS {name}")
else:
PASS += 1
print(f" PASS {name} ({result})")
except Exception as e:
FAIL += 1
print(f" FAIL {name}: {e}")
return fn
return decorator
# ─── 测试用例 ───
@test("1.1 Embedding 服务 (SiliconFlow BGE-M3)")
def test_embedding_generation():
from app.services.embedding_service import embedding_service
emb = asyncio.run(embedding_service.generate_embedding("天工智能体平台记忆测试"))
assert emb and len(emb) == 1024, f"期望 1024 维,实际 {len(emb) if emb else 0}"
return f"OK dims=1024"
@test("1.2 离线关键词分词器")
def test_offline_tokenizer():
from app.services.embedding_service import embedding_service
# 中文二元组
tokens = embedding_service._tokenize("今天天气真好")
assert "今天" in tokens or "天气" in tokens, "中文二元组缺失"
assert len(tokens) > 2, f"tokens太少: {len(tokens)}"
# 混合中英文
tokens = embedding_service._tokenize("Python写代码")
assert "python" in tokens, f"英文token缺失: {tokens}"
# 数字提取
tokens = embedding_service._tokenize("IP: 101.43.95.130")
assert "101" in tokens and "130" in tokens, f"数字token缺失: {tokens}"
return f"OK tokens={len(tokens)}"
@test("1.3 离线关键词搜索")
def test_keyword_search():
from app.services.embedding_service import embedding_service
entries = [
{"id": "1", "content_text": "数据库地址是101.43.95.130", "embedding": [], "metadata": {}},
{"id": "2", "content_text": "今天天气很好适合出去玩", "embedding": [], "metadata": {}},
{"id": "3", "content_text": "Python是一门很好的编程语言", "embedding": [], "metadata": {}},
{"id": "4", "content_text": "天工平台有7个飞书机器人", "embedding": [], "metadata": {}},
]
results = embedding_service.keyword_search("Python编程", entries, top_k=2)
assert len(results) > 0, "关键词搜索无结果"
assert "Python" in results[0]["content_text"], f"首条结果不相关: {results[0]['content_text'][:50]}"
results = embedding_service.keyword_search("飞书机器人", entries, top_k=2)
assert any("飞书机器人" in r["content_text"] for r in results), "飞书搜索失败"
return f"OK 命中{len(results)}"
@test("2.1 记忆类型推断 (P0)")
def test_memory_type_inference():
from app.agent_runtime.memory import AgentMemory
cases = [
("我喜欢用Python写代码", "好的", "user"),
("这个功能报错了,不对", "让我看看", "feedback"),
("数据库的地址是什么?", "地址是101.43.95.130", "reference"),
("这个任务的进度怎么样了?", "任务完成80%", "project"),
("帮我提交一下代码", "已提交", "project"),
("记住我不喜欢吃辣", "记住了", "user"),
]
for um, ar, expected in cases:
result = AgentMemory._infer_memory_type(um, ar)
assert result == expected, f"\"{um[:20]}\" 期望 {expected},实际 {result}"
return f"OK {len(cases)} cases"
@test("2.2 记忆类型过滤")
def test_memory_type_filter():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test_filter", memory_type_filter=["user", "feedback"])
assert mem.memory_type_filter == ["user", "feedback"]
assert mem.MEMORY_TYPES == ("user", "feedback", "project", "reference")
# 无过滤
mem2 = AgentMemory(scope_id="test_nofilter")
assert mem2.memory_type_filter is None
return "OK"
@test("3.1 LLM Rerank 配置 (P2)")
def test_rerank_config():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test_rerank", vector_memory_rerank=True)
assert mem.vector_memory_rerank is True
assert hasattr(mem, "_llm_rerank"), "缺少 _llm_rerank 方法"
mem2 = AgentMemory(scope_id="test_norerank")
assert mem2.vector_memory_rerank is False
return "OK"
@test("3.2 消息裁剪保留配对完整性")
def test_trim_messages():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test", max_history=4)
# 构造含 tool_calls + tool_result 的消息序列
msgs = [
{"role": "system", "content": "你是助手"},
{"role": "user", "content": "查天气"},
{"role": "assistant", "content": "好的", "tool_calls": [{"name": "get_weather", "id": "1"}]},
{"role": "tool", "content": "晴天 25度", "tool_call_id": "1"},
{"role": "assistant", "content": "今天晴天25度"},
{"role": "user", "content": "谢谢"},
]
trimmed = mem.trim_messages(msgs)
# system msg 应保留
assert trimmed[0]["role"] == "system", "system消息应保留"
# 不应有孤立的 tool 消息开头
assert trimmed[1]["role"] != "tool", "裁剪后首条不应是孤立 tool 消息"
return f"OK trimmed to {len(trimmed)}"
@test("4.1 后台异步压缩结构完整 (P3)")
def test_background_compress_structure():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test_bg")
assert hasattr(mem, "_background_compress_and_save"), "缺少 _background_compress_and_save"
assert hasattr(mem, "_compress_and_summarize"), "缺少 _compress_and_summarize"
assert hasattr(mem, "_save_compressed_memories"), "缺少 _save_compressed_memories (P1)"
return "OK"
@test("5.1 Auto Dream 阈值配置 (P4)")
def test_auto_dream_config():
from app.services.auto_dream_service import MERGE_SIMILARITY_THRESHOLD, _should_dream_today
assert 0.8 <= MERGE_SIMILARITY_THRESHOLD <= 0.95, "合并阈值不合理"
# 非凌晨3点不应触发
assert _should_dream_today() is False, "非凌晨3点不应触发 dream"
return f"OK threshold={MERGE_SIMILARITY_THRESHOLD}"
@test("5.2 Auto Dream 服务导入正常")
def test_auto_dream_import():
from app.services.auto_dream_service import run_auto_dream, _should_dream_today
assert callable(run_auto_dream)
assert callable(_should_dream_today)
return "OK"
@test("6.1 团队共享记忆 (P6)")
def test_team_sharing():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="agent_1", team_id="team_alpha", team_share_enabled=True)
assert mem.team_id == "team_alpha"
assert mem.team_share_enabled is True
mem2 = AgentMemory(scope_id="agent_2", team_id="team_alpha", team_share_enabled=False)
assert mem2.team_id == "team_alpha"
assert mem2.team_share_enabled is False
return "OK"
@test("7.1 文件式记忆存储 (P7)")
def test_file_memory_store():
from app.services.file_memory_service import FileMemoryStore
tmpdir = tempfile.mkdtemp(prefix="tmem_")
try:
store = FileMemoryStore(tmpdir)
# 保存
store.save("用户偏好", "用户喜欢用Python开发", mem_type="user")
store.save("数据库配置", "MySQL地址101.43.95.130", mem_type="reference")
store.save("项目信息", "天工平台有7个飞书机器人", mem_type="project")
# 计数
assert store.memory_count == 3, f"期望 3实际 {store.memory_count}"
# 搜索
results = store.search("Python")
assert len(results) > 0, "Python搜索无结果"
results = store.search("飞书机器人")
assert len(results) > 0, "飞书搜索无结果"
# 按类型列出
user_items = store.list_by_type("user")
assert len(user_items) >= 1, "user类型缺失"
# 删除
store.delete("用户偏好")
assert store.memory_count == 2, f"删除后期望 2实际 {store.memory_count}"
# MEMORY.md 存在
index_path = os.path.join(tmpdir, "MEMORY.md")
assert os.path.exists(index_path), "MEMORY.md 不存在"
return f"OK saved=3 searched=2 deleted=1"
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
@test("7.2 文件记忆读取")
def test_file_memory_read():
from app.services.file_memory_service import FileMemoryStore
tmpdir = tempfile.mkdtemp(prefix="tmem_")
try:
store = FileMemoryStore(tmpdir)
store.save("测试记忆", "这是一条测试记忆内容包含关键词Python和天工", mem_type="reference")
# 通过搜索读取
results = store.search("Python")
assert len(results) == 1
assert results[0]["source"] == "file"
assert "Python" in results[0]["content"]
return f"OK content={results[0]['content'][:30]}"
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
@test("8.1 余弦相似度计算")
def test_cosine_similarity():
from app.services.embedding_service import embedding_service
# 相同向量
sim = embedding_service.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
assert abs(sim - 1.0) < 0.001, f"相同向量相似度应为1.0,实际{sim}"
# 正交向量
sim = embedding_service.cosine_similarity([1.0, 0.0], [0.0, 1.0])
assert abs(sim - 0.0) < 0.001, f"正交向量相似度应为0.0,实际{sim}"
# 维度不同
sim = embedding_service.cosine_similarity([1.0], [1.0, 2.0])
assert sim == 0.0, f"不同维度应返回0"
# 空向量
sim = embedding_service.cosine_similarity([], [1.0, 2.0])
assert sim == 0.0, "空向量应返回0"
return "OK"
@test("9.1 压缩记忆向量化可调用 (P1)")
def test_compressed_memory_vectorization():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test_cmv")
assert hasattr(mem, "_save_compressed_memories")
assert callable(mem._save_compressed_memories)
return "OK"
@test("9.2 全局知识保存结构")
def test_global_knowledge_structure():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(scope_id="test_gk")
assert hasattr(mem, "save_global_knowledge")
assert hasattr(mem, "_global_knowledge_search")
return "OK"
@test("10.1 完整记忆生命周期模拟")
def test_full_lifecycle():
"""模拟一次完整的记忆生命周期:创建 → 检索 → 保存 → 压缩 → 整合"""
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(
scope_id="test_lifecycle",
vector_memory_enabled=True,
vector_memory_top_k=3,
vector_memory_rerank=False,
memory_type_filter=None,
team_id="test_team",
team_share_enabled=True,
memory_dir_enabled=True,
memory_dir_path=tempfile.mkdtemp(prefix="tlife_"),
)
# 初始化
text = asyncio.run(mem.initialize("Python开发"))
assert isinstance(text, str), "initialize 应返回字符串"
# 保存上下文
asyncio.run(mem.save_context(
"我喜欢用Python写代码",
"Python确实是很好的选择",
))
# 消息裁剪
msgs = [
{"role": "system", "content": "你是助手"},
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!"},
{"role": "user", "content": "帮我写Python"},
{"role": "assistant", "content": "好的"},
{"role": "user", "content": "谢谢"},
]
trimmed = mem.trim_messages(msgs)
assert len(trimmed) <= mem.max_history + 1, "裁剪后应不超过 max_history"
# 清理文件记忆目录
mp = mem.memory_dir_path
if mp and os.path.exists(mp):
shutil.rmtree(mp, ignore_errors=True)
return "OK init+save+trim"
@test("10.2 AgentMemory 配置全量传递")
def test_full_config_wiring():
from app.agent_runtime.memory import AgentMemory
mem = AgentMemory(
scope_kind="agent",
scope_id="agent_78ba9dfb",
session_key="session_001",
persist=True,
max_history=15,
vector_memory_enabled=True,
vector_memory_top_k=8,
vector_memory_rerank=True,
memory_type_filter=["user", "project"],
team_id="team_feishu",
team_share_enabled=True,
memory_dir_enabled=True,
memory_dir_path="/tmp/tiangong_mem",
)
assert mem.scope_kind == "agent"
assert mem.scope_id == "agent_78ba9dfb"
assert mem.max_history == 15
assert mem.vector_memory_top_k == 8
assert mem.vector_memory_rerank is True
assert mem.memory_type_filter == ["user", "project"]
assert mem.team_id == "team_feishu"
assert mem.team_share_enabled is True
assert mem.memory_dir_enabled is True
assert mem.memory_dir_path == "/tmp/tiangong_mem"
return "OK all 12 params"
# ─── 运行 ───
if __name__ == "__main__":
print("=" * 60)
print("天工 Agent 记忆系统 — 全功能测试")
print("=" * 60)
print()
# 所有 @test 装饰器在 import 时自动执行
total = PASS + FAIL + SKIP
print()
print("=" * 60)
print(f"测试结果: {PASS} 通过 / {FAIL} 失败 / {SKIP} 跳过 (共 {total})")
print("=" * 60)
if FAIL > 0:
sys.exit(1)
else:
print("\n全部测试通过!")