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

103 lines
3.6 KiB
Python

# -*- coding: utf-8 -*-
"""extractor DEDUP 路径:多条 memory embed 必须并行,并记录 embed 耗时 / meta。"""
from __future__ import annotations
import asyncio
import json
from typing import Any, Dict, List
from hy_memory.pipelines.base import WriteRequest
from hy_memory.pipelines.writer import MemoryWriter
def run(coro):
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
loop.close()
class _FakeCache:
def __init__(self):
self.logs: List[Dict[str, Any]] = []
async def store_pipeline_log(self, **kwargs):
self.logs.append(kwargs)
class TestDedupExtractedParallelEmbed:
def test_dedup_extracted_uses_write_batch_queue(self):
"""N 条文本并发进入 write batch queue,并把向量交给后续阶段。"""
async def _test():
delay = 0.08
in_flight = 0
max_in_flight = 0
call_order: List[str] = []
class _FakeEmbed:
async def embed_queued_with_meta(self, text: str):
nonlocal in_flight, max_in_flight
call_order.append(text)
in_flight += 1
max_in_flight = max(max_in_flight, in_flight)
await asyncio.sleep(delay)
in_flight -= 1
idx = int(text.split("-")[1])
vec = [0.0] * 8
vec[idx % 8] = 1.0
return vec, {
"http_status": 200,
"model": "fake-embed",
"usage": {"prompt_tokens": 3, "total_tokens": 3},
"response_headers": {"x-request-id": f"req-{idx}"},
"cache_hit": False,
}
cache = _FakeCache()
writer = MemoryWriter.__new__(MemoryWriter)
writer._embed_service = _FakeEmbed() # type: ignore[assignment]
writer._cache = cache
texts = [f"memory-{i}-content-about-topic-{i}" for i in range(6)]
metas: List[Dict[str, Any]] = [
{"content": t, "layer": "L2_FACT", "tags": []} for t in texts
]
req = WriteRequest(
content="irrelevant",
user_id="u1",
agent_id="a1",
)
t0 = asyncio.get_event_loop().time()
out_texts, out_metas, out_embeddings = await writer._dedup_extracted(
texts, metas, req, req_id="rq-parallel",
)
wall = asyncio.get_event_loop().time() - t0
assert out_texts == texts
assert len(out_metas) == 6
assert set(out_embeddings) == set(texts)
assert set(call_order) == set(texts)
assert max_in_flight >= 2
assert wall < 0.30, f"expected parallel wall <0.30s, got {wall:.3f}s"
assert len(cache.logs) == 1
parsed = json.loads(cache.logs[0]["parsed"])
assert parsed["trigger"] == "extractor"
assert parsed["embedding_source"] == "embed_service"
assert parsed["embed_count"] == 6
assert parsed["embed_ok_count"] == 6
assert isinstance(parsed["embed_elapsed_ms"], (int, float))
assert parsed["embed_elapsed_ms"] < 300
assert len(parsed["embed_metas"]) == 6
for m in parsed["embed_metas"]:
assert "embedding" not in m
assert m.get("model") == "fake-embed"
assert "elapsed_ms" in m
assert "response_headers" in m
run(_test())