127 lines
4.7 KiB
Python
127 lines
4.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
intention (L7) 写入可观测测试。
|
||
|
||
意图由 System 1 直接 upsert 到 VDB(不走 reconcile),此前既不进 pipeline_logs
|
||
也不进 memory_operations。本测试验证 writer._store_intentions 现在会:
|
||
- 落 INTENTION_UPSERT pipeline log(含 memory_id + 回读校验 verified)
|
||
- 落 memory_operations(op=INTENTION_UPSERT,含 memory_id),供线上按
|
||
request_id / memory_id 追查。
|
||
|
||
注意:cache 安装 observability 时会捕获 owner_loop;跨 loop 派发会落空。因此
|
||
本测试把「建 cache/writer + 跑 _store_intentions + 读回」全放进同一个 event loop。
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import os
|
||
import shutil
|
||
import tempfile
|
||
import uuid
|
||
|
||
from hy_memory.pipelines.base import WriteRequest
|
||
from hy_memory.models.memory import MemoryNode
|
||
|
||
|
||
class _FakeEmbed:
|
||
"""给不同 text 返回**明显不同**的向量(每条自己一维是 1.0,其余 0)。
|
||
这样任意两条 cosine=0,绝不会撞上 0.95 dedup 阈值。
|
||
"""
|
||
|
||
def __init__(self):
|
||
self._indices = {}
|
||
|
||
def _vec_for(self, text: str):
|
||
# 32 维稀疏 one-hot:按首次出现顺序分配,避免 hash 随机化或碰撞。
|
||
vec = [0.0] * 32
|
||
idx = self._indices.setdefault(text, len(self._indices)) % 32
|
||
vec[idx] = 1.0
|
||
return vec
|
||
|
||
async def embed_batch(self, texts):
|
||
return [self._vec_for(t) for t in texts]
|
||
|
||
async def embed_queued(self, text):
|
||
return self._vec_for(text)
|
||
|
||
|
||
class _FakeVectorStore:
|
||
"""内存 VDB:upsert 存进 dict,get_by_id 回读。"""
|
||
|
||
def __init__(self):
|
||
self._store = {}
|
||
|
||
async def upsert(self, node: MemoryNode) -> str:
|
||
nid = node.node_id or uuid.uuid4().hex
|
||
node.node_id = nid
|
||
self._store[nid] = node
|
||
return nid
|
||
|
||
async def get_by_id(self, node_id):
|
||
return self._store.get(node_id)
|
||
|
||
|
||
def test_intention_upsert_emits_pipeline_log_and_operation(monkeypatch):
|
||
d = tempfile.mkdtemp(prefix="intent_obs_")
|
||
monkeypatch.setenv("MEMORY_DEDUP_THRESHOLD", "0.95")
|
||
os.environ["MEMORY_PIPELINE_TRACE_ENABLED"] = "true"
|
||
os.environ["MEMORY_WRITE_APPLY_VERIFY_ENABLED"] = "true"
|
||
os.environ["MEMORY_MEMORY_OPERATIONS_ENABLED"] = "true" # 审计表默认关,测试显式开
|
||
os.environ["MEMORY_LOG_DIR"] = os.path.join(d, "logs")
|
||
|
||
async def _main():
|
||
from hy_memory.config import MemoryConfig
|
||
from hy_memory.data.cache_sqlite import SqliteCache
|
||
from hy_memory.utils.pipeline_observability import (
|
||
install_pipeline_observability, drain_pipeline_log_tasks,
|
||
)
|
||
from hy_memory.pipelines.writer import MemoryWriter
|
||
|
||
cfg = MemoryConfig.from_env()
|
||
cfg.cache.db_path = os.path.join(d, "cache.db")
|
||
cache = SqliteCache(cfg)
|
||
await cache.initialize()
|
||
install_pipeline_observability(cache, log_label="test")
|
||
vs = _FakeVectorStore()
|
||
writer = MemoryWriter(cfg, embed_service=_FakeEmbed(), vector_store=vs, cache=cache)
|
||
|
||
rid = uuid.uuid4().hex
|
||
req = WriteRequest(
|
||
content="", messages=[], user_id="u1", agent_id="codebuddy",
|
||
session_id="s1", request_id=rid,
|
||
)
|
||
intentions = [
|
||
{"content": "用户下周二要面试字节", "tags": ["求职"], "valid_until": None},
|
||
{"content": "用户计划学习 Rust", "tags": [], "valid_until": None},
|
||
]
|
||
ids = await writer._store_intentions(intentions, req, vs, rid)
|
||
await drain_pipeline_log_tasks()
|
||
|
||
logs = await cache.get_pipeline_logs(request_id=rid)
|
||
ops_by_id = {nid: await cache.get_memory_operations(memory_id=nid) for nid in ids}
|
||
await cache.close()
|
||
return ids, logs, ops_by_id
|
||
|
||
try:
|
||
ids, logs, ops_by_id = asyncio.new_event_loop().run_until_complete(_main())
|
||
finally:
|
||
shutil.rmtree(d, ignore_errors=True)
|
||
|
||
assert len(ids) == 2
|
||
|
||
# 1. pipeline log: 两条 INTENTION_UPSERT,各带 memory_id + verified=True
|
||
intent_steps = [l for l in logs if l["step"] == "INTENTION_UPSERT"]
|
||
assert len(intent_steps) == 2
|
||
for l in intent_steps:
|
||
assert l.get("memory_ids"), "INTENTION_UPSERT 必须带 memory_id 供追查"
|
||
parsed = json.loads(l["parsed"]) if isinstance(l["parsed"], str) else l["parsed"]
|
||
assert parsed["op"] == "INTENTION_UPSERT"
|
||
assert parsed["memory_id"] in ids
|
||
assert parsed["layer"] == "l7_intention"
|
||
assert parsed["verified"] is True
|
||
|
||
# 2. memory_operations: 两条 op=INTENTION_UPSERT,可按 memory_id 反查
|
||
for nid in ids:
|
||
assert any(o["op"] == "INTENTION_UPSERT" for o in ops_by_id[nid]), \
|
||
f"memory_operations 缺 INTENTION_UPSERT for {nid}"
|