revert(memory): Hindsight document_id 改回每轮独立文档

commit dba821a 把 document_id 从带时间戳改成按 thread_id 确定性派生
(同一 thread 累积成一个文档)。经复核,改回原来的带微秒时间戳写法 ——
每轮对话的 retain 重新落到各自独立的 Hindsight 文档。

retain_every_n_turns 轮次节流(turn_index)与 aiohttp 客户端泄漏修复
保持不动,仅回退 document_id 这一处。

test_hindsight_provider.py 的 TestDocumentId 按"每轮独立文档"重写。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xuhdandClaude Opus 4.7 committed 2026-05-18 15:24:13 +08:00
1 parent dba821a70e
commit 4aee413680
2 files changed
+22 -26

No files matched your search

@@ -20,8 +20,8 @@ HTTP 客户端方式接入一个用户自起的 Hindsight Docker 实例 (``local
document_id 与轮次计数
======================
本 provider 被 ``build_memory_manager`` 每轮重新构造 —— 是无状态的。因此:
- ``document_id`` 由 ``thread_id`` **确定性**派生,同一 thread 的所有轮次 retain
都落到同一个 Hindsight 文档,累积而非每轮碎片化成新文档。
- ``document_id`` 每次 ``initialize`` 用带时间戳的新值,每轮对话的 retain 落到
各自独立的 Hindsight 文档。
- ``retain_every_n_turns`` 的轮次判定依赖外部传入的 ``turn_index``
(由 ``MemoryMiddleware`` 按对话历史算出);未传时退回进程内 ``_turn_counter``。
@@ -168,14 +168,14 @@ class HindsightProvider(MemoryProvider):
return name.strip("-") or "deerflow"
def _reset_retain_batch(self) -> None:
"""重置 retain 批次状态。
"""重置 retain 批次状态 (新的 document_id + 轮次清零)。
``document_id`` 由 ``thread_id`` **确定性**派生 —— provider 虽然被每轮
重新构造,同一 thread 的所有轮次仍落到同一个 Hindsight 文档,累积成一份
而不是碎片化成一轮一个文档。无 thread 时 (例如 memory 工具镜像) 用统一
的兜底 id。轮次计数清零,仅作为 ``turn_index`` 缺失时的退路。
每次 ``initialize`` 用带时间戳的新 ``document_id`` —— 每轮对话的 retain
落到各自独立的 Hindsight 文档。轮次计数清零,仅作为 ``turn_index`` 缺失
时的退路。
"""
self._document_id = f"thread-{self._thread_id}" if self._thread_id else "thread-default"
ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
self._document_id = f"{self._thread_id or 'thread'}-{ts}"
self._turn_counter = 0
def shutdown(self) -> None:
@@ -108,16 +108,7 @@ class TestBankResolution:
class TestDocumentId:
"""document_id 必须由 thread_id 确定性派生 —— provider 每轮重建后仍稳定。"""
def test_deterministic_from_thread_id(self):
# 同一 thread,两个独立构造的 provider 必须算出同一个 document_id
p1 = HindsightProvider(HindsightMemoryConfig(api_url="http://x"))
p1.initialize(user_id="u", agent_id="a", thread_id="t-123", base_dir="")
p2 = HindsightProvider(HindsightMemoryConfig(api_url="http://x"))
p2.initialize(user_id="u", agent_id="a", thread_id="t-123", base_dir="")
assert p1._document_id == p2._document_id
assert "t-123" in p1._document_id
"""document_id 每次 initialize 带时间戳 —— 每轮对话落到各自独立的文档。"""
def test_different_threads_get_different_doc(self):
p1 = HindsightProvider(HindsightMemoryConfig(api_url="http://x"))
@@ -126,28 +117,34 @@ class TestDocumentId:
p2.initialize(user_id="u", agent_id="a", thread_id="t-2", base_dir="")
assert p1._document_id != p2._document_id
def test_doc_id_carries_thread_prefix(self):
p = HindsightProvider(HindsightMemoryConfig(api_url="http://x"))
p.initialize(user_id="u", agent_id="a", thread_id="t-123", base_dir="")
# document_id 形如 "t-123-{时间戳}"
assert p._document_id.startswith("t-123-")
def test_no_thread_id_uses_fallback(self):
p = HindsightProvider(HindsightMemoryConfig(api_url="http://x"))
p.initialize(user_id="u", agent_id="a", thread_id="", base_dir="")
assert p._document_id == "thread-default"
# 无 thread 时前缀塌缩成 "thread-"
assert p._document_id.startswith("thread-")
def test_retain_writes_thread_document_id(self):
# sync_turn 落盘时带的 document_id 应是 thread 确定性 id,不含时间戳
# sync_turn 落盘时带的 document_id 形如 "t1-{时间戳}"
client = _FakeClient()
provider = _make_provider(client) # thread_id="t1"
provider.sync_turn("用户说啥", "助手答啥")
assert client.retained[0]["document_id"] == "thread-t1"
assert client.retained[0]["document_id"].startswith("t1-")
def test_recreated_providers_share_one_document(self):
# 模拟真实场景:同一 thread 连续 3 轮,每轮 provider 重建,
# 三轮 retain 必须落到同一个文档。
def test_recreated_providers_get_distinct_documents(self):
# 同一 thread 连续 3 轮、每轮 provider 重建 —— 各自落到独立文档
doc_ids: set[str] = set()
for turn in range(1, 4):
client = _FakeClient()
provider = _make_provider(client) # 每轮重建,thread_id 固定 t1
provider.sync_turn(f"u{turn}", f"a{turn}")
doc_ids.add(client.retained[0]["document_id"])
assert doc_ids == {"thread-t1"}
assert len(doc_ids) == 3
class TestPrefetchRecall:
@@ -334,7 +331,6 @@ class TestSessionSwitch:
assert provider._thread_id == "t2"
assert provider._turn_counter == 0
assert provider._document_id != old_doc
assert provider._document_id == "thread-t2"
class TestMisc: