Files
Hy-Memory/tests/test_update_payload_embedding.py
2026-10-16 16:26:27 +08:00

153 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
update_payload 同时更新 embedding 的后端验证(chroma / qdrant / faiss)。
UPDATE 改为原地更新后,update_payload(node_id, {"content":..., "embedding": new_vec})
必须真正把库里的向量也换掉。本测试对每个后端:
1. upsert 一个向量为 v_old 的节点;
2. update_payload 把 embedding 换成 v_new(与 v_old 近正交);
3. 用 v_new 查询 → 该节点 cosine≈1 命中;用 v_old 查询 → 不再高分;
4. get_embeddings 取回的向量 == v_new。
"""
import asyncio
import uuid
import pytest
from hy_memory.config import MemoryConfig
from hy_memory.models.memory import MemoryNode, MemoryLayer, MemoryStatus
def run(coro):
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
loop.close()
def _unit(vec):
n = sum(x * x for x in vec) ** 0.5 or 1.0
return [x / n for x in vec]
# 32 维:v_old 指向轴0,v_new 指向轴1(正交)
def _v_old():
return _unit([1.0] + [0.0] * 31)
def _v_new():
return _unit([0.0, 1.0] + [0.0] * 30)
def _mk_node(emb):
n = MemoryNode(
node_id="n1", user_id="u1", agent_id="a1", session_id="s1",
content="原始内容", layer=MemoryLayer.L2_FACT,
status=MemoryStatus.ACTIVE, is_latest=True,
)
n.embedding = emb
return n
def _check_update_with_embedding(store):
"""通用断言:update_payload 带 embedding 后,向量确实被替换。"""
run(store.upsert(_mk_node(_v_old())))
# 原地更新:换内容 + 换向量
ok = run(store.update_payload("n1", {
"content": "更新后的内容",
"embedding": _v_new(),
}))
assert ok is True
# 1) get_embeddings 取回的应是 v_new
embs = run(store.get_embeddings(["n1"]))
assert "n1" in embs
got = embs["n1"]
# 与 v_new 点积≈1,与 v_old 点积≈0
dot_new = sum(a * b for a, b in zip(got, _v_new()))
dot_old = sum(a * b for a, b in zip(got, _v_old()))
assert dot_new > 0.99, f"stored vector should match v_new, dot={dot_new}"
assert abs(dot_old) < 0.01, f"stored vector should NOT match v_old, dot={dot_old}"
# 2) 用 v_new 查询应命中 n1(高分)
hits_new = run(store.search(query_embedding=_v_new(), user_id="u1", limit=5))
ids_new = {h["node_id"]: h["score"] for h in hits_new}
assert "n1" in ids_new
assert ids_new["n1"] > 0.99
# 3) content 也更新了
node = run(store.get_by_id("n1"))
assert node.content == "更新后的内容"
class TestChromaUpdateEmbedding:
def test_update_payload_with_embedding(self, tmp_path):
from hy_memory.data.vector_store_chroma import ChromaVectorStore
cfg = MemoryConfig.from_dict({"vector_store": {
"provider": "chroma",
"collection_name": f"t_upd_{uuid.uuid4().hex[:8]}",
"persist_directory": str(tmp_path / "chroma"),
"embedding_dims": 32,
}})
vs = ChromaVectorStore(cfg)
run(vs.initialize())
try:
_check_update_with_embedding(vs)
finally:
run(vs.close())
def _qdrant_ok():
try:
import qdrant_client # noqa
return True
except Exception:
return False
@pytest.mark.skipif(not _qdrant_ok(), reason="qdrant-client not installed")
class TestQdrantUpdateEmbedding:
def test_update_payload_with_embedding(self, tmp_path):
from hy_memory.data.vector_store_qdrant import QdrantVectorStore
cfg = MemoryConfig.from_dict({"vector_store": {
"provider": "qdrant",
"collection_name": f"t_upd_{uuid.uuid4().hex[:8]}",
"persist_directory": str(tmp_path / "qdrant"),
"embedding_dims": 32,
}})
vs = QdrantVectorStore(cfg)
run(vs.initialize())
try:
_check_update_with_embedding(vs)
finally:
run(vs.close())
def _faiss_ok():
try:
import faiss # noqa
return True
except Exception:
return False
@pytest.mark.skipif(not _faiss_ok(), reason="faiss not installed")
class TestFaissUpdateEmbedding:
def test_update_payload_with_embedding(self, tmp_path):
from hy_memory.data.vector_store_faiss import FaissVectorStore
cfg = MemoryConfig.from_dict({"vector_store": {
"provider": "faiss",
"collection_name": f"t_upd_{uuid.uuid4().hex[:8]}",
"persist_directory": str(tmp_path / "faiss"),
"embedding_dims": 32,
}})
vs = FaissVectorStore(cfg)
run(vs.initialize())
try:
_check_update_with_embedding(vs)
finally:
run(vs.close())