mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 14:00:35 +00:00
fix(memory): require facts in replacement imports (#8131)
* fix(memory): require facts in replacement imports * docs(memory): document import facts requirement * docs(changelog): link memory import fix
This commit is contained in:
parent
2a97ec3e58
commit
d9b2cb4bcb
@ -238,6 +238,10 @@ This section accumulates work toward the **2.3.0** milestone
|
||||
group binding, which covers every group of the bot, starts a new one in
|
||||
each group. ([#7794])
|
||||
|
||||
- **memory:** Importing a backup without a `facts` array now returns HTTP 422
|
||||
and preserves saved memory. An explicit empty array still clears the selected
|
||||
fact bucket. ([#8131])
|
||||
|
||||
- **memory:** An agent-scoped memory write whose `agent_name` is a Windows
|
||||
reserved device name (`NUL`, `CON`, `COM1`, ...) is now rejected with a `422`
|
||||
on the two routes that create that scope, `POST /memory/facts` and
|
||||
@ -16299,3 +16303,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#8071]: https://github.com/bytedance/deer-flow/issues/8071
|
||||
[#8072]: https://github.com/bytedance/deer-flow/pull/8072
|
||||
[#8126]: https://github.com/bytedance/deer-flow/pull/8126
|
||||
[#8131]: https://github.com/bytedance/deer-flow/pull/8131
|
||||
|
||||
@ -38,6 +38,8 @@ make test-blocking-io # after adding any file, config
|
||||
- **Imports create, they do not overwrite.** Agent and skill imports are scoped to
|
||||
the caller and return 409 on a name collision. Exports leave out credentials,
|
||||
memory, conversations and installation bindings.
|
||||
- **Memory backup imports** replace the selected facts and require an explicit
|
||||
`facts` array. Omitting it returns 422. An empty array clears that bucket.
|
||||
- **Outbound requests from a route** (model discovery, connection tests) have a
|
||||
time limit and a size limit, do not follow redirects, and return redacted
|
||||
provider errors.
|
||||
|
||||
@ -120,6 +120,12 @@ class MemoryResponse(BaseModel):
|
||||
facts: list[Fact] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MemoryImportRequest(MemoryResponse):
|
||||
"""Replacement imports must explicitly supply the facts to restore."""
|
||||
|
||||
facts: list[Fact] = Field(description="Facts to restore. An empty list explicitly clears the selected fact bucket.")
|
||||
|
||||
|
||||
def _map_memory_fact_value_error(exc: ValueError) -> HTTPException:
|
||||
"""Convert updater validation errors into stable API responses."""
|
||||
if exc.args and exc.args[0] == "confidence":
|
||||
@ -566,10 +572,13 @@ async def export_memory(request: Request, agent_name: str | None = None) -> Memo
|
||||
response_model=MemoryResponse,
|
||||
response_model_exclude_none=True,
|
||||
summary="Import Memory Data",
|
||||
description="Import the default memory document, or replace only one selected agent's facts while preserving shared summaries.",
|
||||
description=(
|
||||
"Import the default memory document, or replace only one selected agent's facts while preserving shared summaries. "
|
||||
"The request requires an explicit `facts` array. Omitting it returns HTTP 422, and an empty array clears the selected fact bucket."
|
||||
),
|
||||
)
|
||||
@require_permission("memory", "write")
|
||||
async def import_memory(body: MemoryResponse, request: Request, agent_name: str | None = None) -> MemoryResponse:
|
||||
async def import_memory(body: MemoryImportRequest, request: Request, agent_name: str | None = None) -> MemoryResponse:
|
||||
"""Import memory data, preserving shared summaries for a selected agent."""
|
||||
manager = await asyncio.to_thread(get_memory_manager)
|
||||
selected_agent = _management_agent_name_or_501(manager, agent_name, for_creation=True)
|
||||
|
||||
@ -223,6 +223,36 @@ def test_import_route_rejects_invalid_revision_without_replacing_memory(tmp_path
|
||||
manager.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("agent_name", [None, "researcher"])
|
||||
def test_import_requires_explicit_facts_to_replace_saved_memory(tmp_path, agent_name: str | None) -> None:
|
||||
app = make_authed_test_app()
|
||||
app.include_router(memory.router)
|
||||
manager = DeerMem(backend_config={"storage_path": str(tmp_path), "token_counting": "char"})
|
||||
params = {"agent_name": agent_name} if agent_name else {}
|
||||
try:
|
||||
with (
|
||||
patch("app.gateway.routers.memory.get_memory_manager", return_value=manager),
|
||||
patch("app.gateway.routers.memory.get_effective_user_id", return_value="alice"),
|
||||
TestClient(app) as client,
|
||||
):
|
||||
created = client.post("/api/memory/facts", params=params, json={"content": "Keep this preference"})
|
||||
assert created.status_code == 200
|
||||
before = client.get("/api/memory/export", params=params).json()
|
||||
incomplete_backup = {key: value for key, value in before.items() if key != "facts"}
|
||||
|
||||
response = client.post("/api/memory/import", params=params, json=incomplete_backup)
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json()["detail"][0]["loc"] == ["body", "facts"]
|
||||
assert client.get("/api/memory/export", params=params).json() == before
|
||||
|
||||
empty_backup = {**incomplete_backup, "facts": []}
|
||||
cleared = client.post("/api/memory/import", params=params, json=empty_backup)
|
||||
assert cleared.status_code == 200
|
||||
assert client.get("/api/memory/export", params=params).json()["facts"] == []
|
||||
finally:
|
||||
manager.close()
|
||||
|
||||
|
||||
def test_import_memory_route_preserves_source_error() -> None:
|
||||
app = make_authed_test_app()
|
||||
app.include_router(memory.router)
|
||||
|
||||
@ -152,6 +152,8 @@ DeerMem backup imports restore saved fact contents even after those facts have
|
||||
been edited, using the current stored revisions to protect the restore. Older
|
||||
backups may omit fact revisions. Backup revisions are validated before the
|
||||
importer substitutes current revisions. Rejected imports preserve stored memory.
|
||||
The import endpoint requires an explicit `facts` array. Omitting it returns
|
||||
HTTP 422, while `facts: []` explicitly clears the selected fact bucket.
|
||||
A concurrent write during import still returns HTTP 409 so the caller can reload
|
||||
and retry.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user