feat(gateway): add Offload V2 result ref recovery (#577)

* feat(gateway): add offload result ref recovery

Signed-off-by: junevanlong <junevanlong@tencent.com>

* fix(gateway): align offload recovery wording

Signed-off-by: junevanlong <junevanlong@tencent.com>

---------

Signed-off-by: junevanlong <junevanlong@tencent.com>
This commit is contained in:
Rememorio
2026-07-24 14:12:53 +08:00
committed by GitHub
parent 6e551419fe
commit e714dfaf8c
19 changed files with 759 additions and 8 deletions
+10
View File
@@ -80,6 +80,15 @@ compacted = client.offload_compact(
)
print(compacted["messages"], compacted["report"])
# Recover an archived tool result referenced by a compacted message
ref = client.offload_read_ref(
session_id="agent_sess_123",
result_ref="offload/agent_sess_123/refs/call_1.md",
query="relevant section",
max_tokens=800,
)
print(ref["content"], ref["truncated"])
# Read memory pipeline artifacts (e.g. persona.md, scene_blocks/*.md)
raw = client.read_file("scene_blocks/工作.md")
```
@@ -122,6 +131,7 @@ asyncio.run(main())
| L3 | `write_core()` | `POST /v2/core/write` |
| Offload | `offload_ingest()` | `POST /v2/offload/ingest` |
| Offload | `offload_compact()` | `POST /v2/offload/compact` |
| Offload | `offload_read_ref()` | `POST /v2/offload/read-ref` |
| Offload | `offload_query_mmd()` | `POST /v2/offload/query-mmd` |
## Error Handling
+10
View File
@@ -80,6 +80,15 @@ compacted = client.offload_compact(
)
print(compacted["messages"], compacted["report"])
# 按压缩消息中的 result_ref 恢复归档工具结果
ref = client.offload_read_ref(
session_id="agent_sess_123",
result_ref="offload/agent_sess_123/refs/call_1.md",
query="相关片段",
max_tokens=800,
)
print(ref["content"], ref["truncated"])
# 读取记忆 pipeline 产物(如 persona.md、scene_blocks/*.md
raw = client.read_file("scene_blocks/工作.md")
```
@@ -122,6 +131,7 @@ asyncio.run(main())
| L3 | `write_core()` | `POST /v2/core/write` |
| Offload | `offload_ingest()` | `POST /v2/offload/ingest` |
| Offload | `offload_compact()` | `POST /v2/offload/compact` |
| Offload | `offload_read_ref()` | `POST /v2/offload/read-ref` |
| Offload | `offload_query_mmd()` | `POST /v2/offload/query-mmd` |
## 错误处理
+65 -2
View File
@@ -255,7 +255,7 @@ class MemoryClient:
"""``POST /core/write``"""
return self._stub.post(f"{_V2}/core/write", {"content": content})
# -- Offload (Ingest + Compact + Query-MMD) ----------------------------
# -- Offload (Ingest + Compact + Read-Ref + Query-MMD) -----------------
def offload_ingest(
self,
@@ -336,6 +336,46 @@ class MemoryClient:
}),
)
def offload_read_ref(
self,
session_id: str,
result_ref: str,
*,
query: Optional[str] = None,
start_line: Optional[int] = None,
end_line: Optional[int] = None,
max_tokens: Optional[int] = None,
) -> Dict[str, Any]:
"""``POST /v2/offload/read-ref`` — 读取已归档的原始工具结果。
服务端会验证 ``result_ref`` 是否属于 ``session_id``,并限制返回内容长度。
``query`` 与行范围参数不能同时使用。
Parameters
----------
session_id : str
归档结果所属的会话 ID。
result_ref : str
Offload V2 压缩结果中返回的引用。
query : str, optional
用于定位片段的子串,匹配时不区分大小写。
start_line, end_line : int, optional
从 1 开始且包含首尾的行范围。
max_tokens : int, optional
返回内容的最大 token 数,服务端另有硬上限。
"""
return self._stub.post(
f"{_V2}/offload/read-ref",
_strip_none({
"session_id": session_id,
"result_ref": result_ref,
"query": query,
"start_line": start_line,
"end_line": end_line,
"max_tokens": max_tokens,
}),
)
def offload_query_mmd(
self,
session_id: str,
@@ -542,7 +582,7 @@ class AsyncMemoryClient:
async def write_core(self, content: str) -> Dict[str, Any]:
return await self._stub.post(f"{_V2}/core/write", {"content": content})
# -- Offload (Ingest + Compact + Query-MMD) ----------------------------
# -- Offload (Ingest + Compact + Read-Ref + Query-MMD) -----------------
async def offload_ingest(
self,
@@ -586,6 +626,29 @@ class AsyncMemoryClient:
}),
)
async def offload_read_ref(
self,
session_id: str,
result_ref: str,
*,
query: Optional[str] = None,
start_line: Optional[int] = None,
end_line: Optional[int] = None,
max_tokens: Optional[int] = None,
) -> Dict[str, Any]:
"""``POST /v2/offload/read-ref``(异步)"""
return await self._stub.post(
f"{_V2}/offload/read-ref",
_strip_none({
"session_id": session_id,
"result_ref": result_ref,
"query": query,
"start_line": start_line,
"end_line": end_line,
"max_tokens": max_tokens,
}),
)
async def offload_query_mmd(
self,
session_id: str,
+85
View File
@@ -0,0 +1,85 @@
from typing import Any, Dict, Optional
import pytest
from tencentdb_agent_memory import AsyncMemoryClient, MemoryClient
from tencentdb_agent_memory._http import Stub
class RecordingStub(Stub):
def __init__(self) -> None:
self.path = ""
self.body: Dict[str, Any] = {}
def post(
self,
path: str,
body: dict,
timeout: Optional[float] = None,
) -> dict:
self.path = path
self.body = body
return {"content": "archived result", "truncated": False}
def close(self) -> None:
pass
class AsyncRecordingStub:
def __init__(self) -> None:
self.path = ""
self.body: Dict[str, Any] = {}
async def post(
self,
path: str,
body: dict,
timeout: Optional[float] = None,
) -> dict:
self.path = path
self.body = body
return {"content": "archived result", "truncated": False}
def test_offload_read_ref_posts_bounded_query_options() -> None:
stub = RecordingStub()
client = MemoryClient(stub=stub)
result = client.offload_read_ref(
"session-1",
"offload/session-1/refs/call-1.md",
query="needle",
max_tokens=800,
)
assert stub.path == "/v2/offload/read-ref"
assert stub.body == {
"session_id": "session-1",
"result_ref": "offload/session-1/refs/call-1.md",
"query": "needle",
"max_tokens": 800,
}
assert result["content"] == "archived result"
@pytest.mark.asyncio
async def test_async_offload_read_ref_posts_line_range() -> None:
stub = AsyncRecordingStub()
client = AsyncMemoryClient.__new__(AsyncMemoryClient)
client._stub = stub
result = await client.offload_read_ref(
"session-1",
"offload/session-1/refs/call-1.md",
start_line=5,
end_line=12,
)
assert stub.path == "/v2/offload/read-ref"
assert stub.body == {
"session_id": "session-1",
"result_ref": "offload/session-1/refs/call-1.md",
"start_line": 5,
"end_line": 12,
}
assert result["content"] == "archived result"