mirror of
https://github.com/TencentCloud/TencentDB-Agent-Memory
synced 2026-07-28 13:14:32 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
## 错误处理
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
@@ -76,6 +76,15 @@ const compacted = await client.offloadCompact({
|
||||
});
|
||||
console.log(compacted.messages, compacted.report);
|
||||
|
||||
// Recover an archived tool result referenced by a compacted message
|
||||
const ref = await client.offloadReadRef({
|
||||
session_id: "agent_sess_123",
|
||||
result_ref: "offload/agent_sess_123/refs/call_1.md",
|
||||
query: "relevant section",
|
||||
max_tokens: 800,
|
||||
});
|
||||
console.log(ref.content, ref.truncated);
|
||||
|
||||
// Offload v2: query MMD task graphs
|
||||
const mmd = await client.offloadQueryMmd({ session_id: "agent_sess_123", limit: 1 });
|
||||
console.log(mmd.current_mmd, mmd.mmds);
|
||||
@@ -104,6 +113,7 @@ const raw = await client.readFile("scene_blocks/工作.md");
|
||||
| L3 | `writeCore()` | `POST /v2/core/write` |
|
||||
| Offload | `offloadIngest()` | `POST /v2/offload/ingest` |
|
||||
| Offload | `offloadCompact()` | `POST /v2/offload/compact` |
|
||||
| Offload | `offloadReadRef()` | `POST /v2/offload/read-ref` |
|
||||
| Offload | `offloadQueryMmd()` | `POST /v2/offload/query-mmd` |
|
||||
|
||||
## Error Handling
|
||||
|
||||
@@ -76,6 +76,15 @@ const compacted = await client.offloadCompact({
|
||||
});
|
||||
console.log(compacted.messages, compacted.report);
|
||||
|
||||
// 按压缩消息中的 result_ref 恢复归档工具结果
|
||||
const ref = await client.offloadReadRef({
|
||||
session_id: "agent_sess_123",
|
||||
result_ref: "offload/agent_sess_123/refs/call_1.md",
|
||||
query: "相关片段",
|
||||
max_tokens: 800,
|
||||
});
|
||||
console.log(ref.content, ref.truncated);
|
||||
|
||||
// Offload v2: 查询任务流程图(MMD)
|
||||
const mmd = await client.offloadQueryMmd({ session_id: "agent_sess_123", limit: 1 });
|
||||
console.log(mmd.current_mmd, mmd.mmds);
|
||||
@@ -104,6 +113,7 @@ const raw = await client.readFile("scene_blocks/工作.md");
|
||||
| L3 | `writeCore()` | `POST /v2/core/write` |
|
||||
| Offload | `offloadIngest()` | `POST /v2/offload/ingest` |
|
||||
| Offload | `offloadCompact()` | `POST /v2/offload/compact` |
|
||||
| Offload | `offloadReadRef()` | `POST /v2/offload/read-ref` |
|
||||
| Offload | `offloadQueryMmd()` | `POST /v2/offload/query-mmd` |
|
||||
|
||||
## 错误处理
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* TencentDB Agent Memory v2 TypeScript SDK — `MemoryClient`.
|
||||
*
|
||||
* 14 methods mapping 1:1 to the v2 data-plane API.
|
||||
* 18 methods mapping 1:1 to the v2 data-plane API.
|
||||
*/
|
||||
|
||||
import { HttpTransport, type HttpTransportOptions } from "./http.js";
|
||||
@@ -29,6 +29,8 @@ import type {
|
||||
OffloadCompactRequest,
|
||||
OffloadIngestData,
|
||||
OffloadIngestRequest,
|
||||
OffloadReadRefData,
|
||||
OffloadReadRefRequest,
|
||||
OffloadQueryMmdData,
|
||||
OffloadQueryMmdRequest,
|
||||
ScenarioFile,
|
||||
@@ -154,7 +156,7 @@ export class MemoryClient {
|
||||
return this.http.post(`${V2}/core/write`, params as unknown as Record<string, unknown>);
|
||||
}
|
||||
|
||||
// -- Offload (Compaction + Ingest) ------------------------------------
|
||||
// -- Offload -----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Send tool pairs (+ optional context) to offload server for L1 processing.
|
||||
@@ -172,6 +174,13 @@ export class MemoryClient {
|
||||
return this.http.post(`${V2}/offload/compact`, stripUndefined(params as unknown as Record<string, unknown>));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover an archived tool result referenced by a compacted message.
|
||||
*/
|
||||
offloadReadRef(params: OffloadReadRefRequest): Promise<OffloadReadRefData> {
|
||||
return this.http.post(`${V2}/offload/read-ref`, stripUndefined(params as unknown as Record<string, unknown>));
|
||||
}
|
||||
|
||||
/**
|
||||
* Query MMD task graphs for a session.
|
||||
* limit=1 returns only the current active MMD (fast path).
|
||||
|
||||
@@ -28,6 +28,8 @@ export type {
|
||||
OffloadToolPair, OffloadRecentMessage,
|
||||
OffloadIngestRequest, OffloadIngestData,
|
||||
OffloadCompactRequest, OffloadCompactData, OffloadCompactReport,
|
||||
OffloadReadRefRequest, OffloadReadRefData,
|
||||
OffloadQueryMmdRequest, OffloadQueryMmdData,
|
||||
// Common
|
||||
ApiResponseEnvelope,
|
||||
} from "./types.js";
|
||||
|
||||
@@ -245,6 +245,22 @@ export interface OffloadCompactData {
|
||||
report: OffloadCompactReport;
|
||||
}
|
||||
|
||||
export interface OffloadReadRefRequest {
|
||||
session_id: string;
|
||||
result_ref: string;
|
||||
query?: string;
|
||||
start_line?: number;
|
||||
end_line?: number;
|
||||
max_tokens?: number;
|
||||
}
|
||||
|
||||
export interface OffloadReadRefData {
|
||||
result_ref: string;
|
||||
content: string;
|
||||
truncated: boolean;
|
||||
match_found?: boolean;
|
||||
}
|
||||
|
||||
export interface OffloadQueryMmdRequest {
|
||||
session_id: string;
|
||||
limit?: number;
|
||||
|
||||
@@ -37,6 +37,11 @@ function createMock(): MockTransport {
|
||||
"/v2/scenario/rm": {},
|
||||
"/v2/core/read": { content: "# core", created_at: "t", updated_at: "t" },
|
||||
"/v2/core/write": { updated_at: "t" },
|
||||
"/v2/offload/read-ref": {
|
||||
result_ref: "offload/s1/refs/call-1.md",
|
||||
content: "archived result",
|
||||
truncated: false,
|
||||
},
|
||||
};
|
||||
return m;
|
||||
}
|
||||
@@ -187,6 +192,32 @@ describe("L3 Core", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Offload
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("Offload", () => {
|
||||
it("offloadReadRef sends the reference and strips undefined filters", async () => {
|
||||
const mock = createMock();
|
||||
const client = new MemoryClient(mock);
|
||||
const result = await client.offloadReadRef({
|
||||
session_id: "s1",
|
||||
result_ref: "offload/s1/refs/call-1.md",
|
||||
query: "result",
|
||||
max_tokens: 800,
|
||||
});
|
||||
|
||||
expect(result.content).toBe("archived result");
|
||||
expect(mock.calls[0]!.path).toBe("/v2/offload/read-ref");
|
||||
expect(mock.calls[0]!.body).toEqual({
|
||||
session_id: "s1",
|
||||
result_ref: "offload/s1/refs/call-1.md",
|
||||
query: "result",
|
||||
max_tokens: 800,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user