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:
1 parent
dba821a70e
commit
4aee413680
2 files changed
+22
-26
No files matched your search
+8
-8
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user