mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-07 19:20:34 +00:00
fix(memory): apply Honcho read policies to malformed search metadata (#7890)
Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
728446fe0d
commit
6538496dbc
@ -19,6 +19,9 @@ to the constructed backend and require backend recreation to change.
|
||||
|
||||
This directory owns memory capture, storage, retrieval, prompt injection, and model-driven memory tools.
|
||||
|
||||
Honcho search rejects non-object, non-null `metadata` at the HTTP boundary:
|
||||
apply the read policy to the whole result, with value-free diagnostics.
|
||||
|
||||
#### Main components
|
||||
|
||||
- `manager.py` defines the backend-neutral `MemoryManager` contract.
|
||||
|
||||
@ -71,6 +71,13 @@ cannot see each other's memory by construction.
|
||||
workspace-scoped search, while the passive per-turn write middleware is
|
||||
retained because Honcho's deriver learns from `add()` writes.
|
||||
|
||||
Search result `metadata` must be an object, null, or absent. The HTTP client
|
||||
rejects other containers before returning results, so `failure_policy.read`
|
||||
also covers metadata-shape failures: fail-open returns no search results and
|
||||
fail-closed raises `MemoryReadError`. A malformed later result does not expose
|
||||
a partial result set. Diagnostics name the result index and field, without
|
||||
echoing metadata or memory content.
|
||||
|
||||
## Incremental conversation capture
|
||||
|
||||
Checkpoint history is cumulative. For messages with stable source IDs, the
|
||||
|
||||
@ -106,4 +106,9 @@ class HonchoClient:
|
||||
if filters:
|
||||
payload["filters"] = filters
|
||||
data = self._post(f"/v3/workspaces/{workspace}/search", payload)
|
||||
return list(data) if isinstance(data, list) else []
|
||||
results = list(data) if isinstance(data, list) else []
|
||||
for index, item in enumerate(results):
|
||||
metadata = item.get("metadata") if isinstance(item, dict) else None
|
||||
if metadata is not None and not isinstance(metadata, dict):
|
||||
raise HonchoRequestError(f"Honcho search results[{index}].metadata must be an object or null")
|
||||
return results
|
||||
|
||||
62
backend/tests/test_honcho_search_metadata_policy.py
Normal file
62
backend/tests/test_honcho_search_metadata_policy.py
Normal file
@ -0,0 +1,62 @@
|
||||
"""Malformed remote search metadata must honor the configured read policy."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from deerflow.agents.memory.backends.honcho.client import HonchoClient
|
||||
from deerflow.agents.memory.backends.honcho.honcho_manager import HonchoMemoryManager
|
||||
from deerflow.agents.memory.manager import MemoryReadError
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("async_read", [False, True])
|
||||
@pytest.mark.parametrize("policy", ["fail_open", "fail_closed"])
|
||||
@pytest.mark.parametrize("metadata", ["private-invalid-metadata", ["private-invalid-metadata"], 7, True])
|
||||
async def test_search_metadata_failure_honors_read_policy(metadata, policy, async_read, caplog):
|
||||
manager = HonchoMemoryManager.from_config({"base_url": "http://honcho.test", "failure_policy": {"read": policy}})
|
||||
manager._client.close()
|
||||
requests = []
|
||||
|
||||
def respond(request):
|
||||
requests.append(request)
|
||||
assert json.loads(request.content) == {"query": "synthetic query", "limit": 5}
|
||||
return httpx.Response(200, json=[{"content": "private-valid-content", "metadata": {"category": "preference"}}, {"content": "private-invalid-content", "metadata": metadata}])
|
||||
|
||||
manager._client = HonchoClient(manager._config, transport=httpx.MockTransport(respond))
|
||||
|
||||
async def search():
|
||||
if async_read:
|
||||
return await manager.asearch("synthetic query", user_id="synthetic-user")
|
||||
return manager.search("synthetic query", user_id="synthetic-user")
|
||||
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING):
|
||||
if policy == "fail_closed":
|
||||
with pytest.raises(MemoryReadError) as error:
|
||||
await search()
|
||||
diagnostics = str(error.value)
|
||||
else:
|
||||
assert await search() == []
|
||||
diagnostics = caplog.text
|
||||
assert "metadata" in diagnostics
|
||||
for secret in ("private-invalid-metadata", "private-valid-content", "private-invalid-content"):
|
||||
assert secret not in diagnostics
|
||||
assert len(requests) == 1
|
||||
finally:
|
||||
manager.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata", [None, {}, {"category": "preference"}])
|
||||
def test_search_preserves_valid_metadata(metadata):
|
||||
manager = HonchoMemoryManager.from_config({"base_url": "http://honcho.test"})
|
||||
manager._client.close()
|
||||
manager._client = HonchoClient(manager._config, transport=httpx.MockTransport(lambda _: httpx.Response(200, json=[{"content": "Synthetic content", "metadata": metadata}])))
|
||||
try:
|
||||
result = manager.search("synthetic query", user_id="synthetic-user")
|
||||
assert result[0]["content"] == "Synthetic content"
|
||||
assert result[0]["category"] == ("preference" if metadata else "memory")
|
||||
finally:
|
||||
manager.close()
|
||||
Loading…
x
Reference in New Issue
Block a user