Add memory management APIs for OpenViking: list, read, write, and delete memories
This commit is contained in:
89
README.md
89
README.md
@ -152,6 +152,10 @@ API=http://127.0.0.1:1934/memory-system
|
|||||||
| `POST` | `/sessions/{session_id}/extract` | 立即触发 OpenViking extract | 需要 |
|
| `POST` | `/sessions/{session_id}/extract` | 立即触发 OpenViking extract | 需要 |
|
||||||
| `GET/POST` | `/sessions/{session_id}/context` | 查询 OpenViking 会话上下文,并用同一 query 搜索 EverOS 记忆 | 需要 |
|
| `GET/POST` | `/sessions/{session_id}/context` | 查询 OpenViking 会话上下文,并用同一 query 搜索 EverOS 记忆 | 需要 |
|
||||||
| `GET/POST` | `/openviking/tasks/{task_id}` | 查询 OpenViking 后台任务状态 | 需要 |
|
| `GET/POST` | `/openviking/tasks/{task_id}` | 查询 OpenViking 后台任务状态 | 需要 |
|
||||||
|
| `GET` | `/memories` | 列出 OpenViking memory URI | 需要 |
|
||||||
|
| `GET` | `/memories/content` | 读取某条 memory 内容 | 需要 |
|
||||||
|
| `POST` | `/memories` | 创建、覆盖或追加写入 memory | 需要 |
|
||||||
|
| `DELETE` | `/memories` | 删除某条 memory URI | 需要 |
|
||||||
| `POST` | `/resources` | 上传本地文件或远程 URL 到 OpenViking resources | 需要 |
|
| `POST` | `/resources` | 上传本地文件或远程 URL 到 OpenViking resources | 需要 |
|
||||||
| `DELETE` | `/resources` | 删除 OpenViking resource URI | 需要 |
|
| `DELETE` | `/resources` | 删除 OpenViking resource URI | 需要 |
|
||||||
| `POST` | `/search` | 同时搜索 OpenViking 和 EverOS 记忆 | 需要 |
|
| `POST` | `/search` | 同时搜索 OpenViking 和 EverOS 记忆 | 需要 |
|
||||||
@ -357,6 +361,91 @@ curl -sS -X POST "$API/openviking/tasks/${TASK_ID}" \
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `GET /memories`
|
||||||
|
|
||||||
|
列出 OpenViking memory URI。网关会调用 OpenViking:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/fs/ls?uri=viking://user/memories&recursive=true
|
||||||
|
X-API-Key: <user_key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Query 参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| `user_id` | string | 是 | 用户 ID |
|
||||||
|
| `user_key` | string | 是 | `/users` 返回的 user key |
|
||||||
|
| `uri` | string | 否 | 要列出的 memory 根 URI,默认 `viking://user/memories` |
|
||||||
|
| `recursive` | bool | 否 | 是否递归列出,默认 `true` |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -G "$API/memories" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories" \
|
||||||
|
--data-urlencode "recursive=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /memories/content`
|
||||||
|
|
||||||
|
读取某条 memory 内容。先用 `/memories` 或 `/search` 找到 `viking://user/memories/...` URI,再读取:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -G "$API/memories/content" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories/preferences/python.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /memories`
|
||||||
|
|
||||||
|
创建、覆盖或追加写入 memory。网关会调用 OpenViking `/api/v1/content/write`,写入后由 OpenViking 刷新语义和向量索引。
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| `user_id` | string | 是 | 用户 ID |
|
||||||
|
| `user_key` | string | 是 | `/users` 返回的 user key |
|
||||||
|
| `uri` | string | 是 | 目标 memory URI,例如 `viking://user/memories/profile.md` |
|
||||||
|
| `content` | string | 是 | 要写入的 Markdown/text 内容 |
|
||||||
|
| `mode` | `create`/`replace`/`append` | 否 | 写入模式,默认 `create` |
|
||||||
|
| `wait` | bool | 否 | 是否等待索引刷新,默认 `true` |
|
||||||
|
|
||||||
|
覆盖修改:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X POST "$API/memories" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_id": "userA",
|
||||||
|
"user_key": "'"$USER_KEY"'",
|
||||||
|
"uri": "viking://user/memories/preferences/python.md",
|
||||||
|
"content": "# Python 偏好\n\n用户偏好使用 Python 做数据分析,常用 pandas。",
|
||||||
|
"mode": "replace",
|
||||||
|
"wait": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
追加补充时把 `mode` 改为 `append`;新增 memory 时可用默认的 `create`。
|
||||||
|
|
||||||
|
### `DELETE /memories`
|
||||||
|
|
||||||
|
删除某条 memory。默认非递归删除;如果 OpenViking 提示目标是目录或复合资源,再把 `recursive` 设为 `true`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X DELETE -G "$API/memories" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories/preferences/python.md" \
|
||||||
|
--data-urlencode "recursive=false"
|
||||||
|
```
|
||||||
|
|
||||||
|
返回中的 `memory` 是 OpenViking 对应接口的原始响应。
|
||||||
|
|
||||||
### `POST /resources`
|
### `POST /resources`
|
||||||
|
|
||||||
上传文件资源到 OpenViking。网关只调用 OpenViking,不写 EverOS。
|
上传文件资源到 OpenViking。网关只调用 OpenViking,不写 EverOS。
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|||||||
|
|
||||||
from .auth import verify_api_key
|
from .auth import verify_api_key
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
|
MemoryWriteRequest,
|
||||||
MessageIngestRequest,
|
MessageIngestRequest,
|
||||||
ProfileRequest,
|
ProfileRequest,
|
||||||
ResourceUploadRequest,
|
ResourceUploadRequest,
|
||||||
@ -80,6 +81,58 @@ async def delete_resource(
|
|||||||
raise user_auth_error(exc) from exc
|
raise user_auth_error(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/memories")
|
||||||
|
async def list_memories(
|
||||||
|
user_id: str = Query(min_length=1),
|
||||||
|
user_key: str = Query(min_length=1),
|
||||||
|
uri: str = Query(default="viking://user/memories", min_length=1),
|
||||||
|
recursive: bool = Query(default=True),
|
||||||
|
service: MemorySystemService = Depends(get_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await service.list_memories(user_id, user_key, uri=uri, recursive=recursive)
|
||||||
|
except PermissionError as exc:
|
||||||
|
raise user_auth_error(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/memories/content")
|
||||||
|
async def read_memory(
|
||||||
|
user_id: str = Query(min_length=1),
|
||||||
|
user_key: str = Query(min_length=1),
|
||||||
|
uri: str = Query(min_length=1),
|
||||||
|
service: MemorySystemService = Depends(get_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await service.read_memory(user_id, user_key, uri)
|
||||||
|
except PermissionError as exc:
|
||||||
|
raise user_auth_error(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/memories")
|
||||||
|
async def write_memory(
|
||||||
|
request: MemoryWriteRequest,
|
||||||
|
service: MemorySystemService = Depends(get_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await service.write_memory(request)
|
||||||
|
except PermissionError as exc:
|
||||||
|
raise user_auth_error(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/memories")
|
||||||
|
async def delete_memory(
|
||||||
|
user_id: str = Query(min_length=1),
|
||||||
|
user_key: str = Query(min_length=1),
|
||||||
|
uri: str = Query(min_length=1),
|
||||||
|
recursive: bool = Query(default=False),
|
||||||
|
service: MemorySystemService = Depends(get_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await service.delete_memory(user_id, user_key, uri, recursive=recursive)
|
||||||
|
except PermissionError as exc:
|
||||||
|
raise user_auth_error(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
@router.post("/sessions/{session_id}/commit")
|
@router.post("/sessions/{session_id}/commit")
|
||||||
async def commit_session(
|
async def commit_session(
|
||||||
session_id: str,
|
session_id: str,
|
||||||
|
|||||||
@ -190,6 +190,62 @@ class OpenVikingMemorySystemClient:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
async def list_memories(
|
||||||
|
self,
|
||||||
|
credential: OpenVikingCredential | str,
|
||||||
|
uri: str = "viking://user/memories",
|
||||||
|
recursive: bool = True,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
async with self._credential_client(credential) as client:
|
||||||
|
response = await client.get(
|
||||||
|
"/api/v1/fs/ls",
|
||||||
|
params={"uri": uri, "recursive": str(recursive).lower()},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def read_memory(self, credential: OpenVikingCredential | str, uri: str) -> dict[str, Any]:
|
||||||
|
async with self._credential_client(credential) as client:
|
||||||
|
response = await client.get("/api/v1/content/read", params={"uri": uri})
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def write_memory(
|
||||||
|
self,
|
||||||
|
credential: OpenVikingCredential | str,
|
||||||
|
*,
|
||||||
|
uri: str,
|
||||||
|
content: str,
|
||||||
|
mode: str = "create",
|
||||||
|
wait: bool = True,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
async with self._credential_client(credential) as client:
|
||||||
|
response = await client.post(
|
||||||
|
"/api/v1/content/write",
|
||||||
|
json={
|
||||||
|
"uri": uri,
|
||||||
|
"content": content,
|
||||||
|
"mode": mode,
|
||||||
|
"wait": wait,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def delete_memory(
|
||||||
|
self,
|
||||||
|
credential: OpenVikingCredential | str,
|
||||||
|
uri: str,
|
||||||
|
recursive: bool = False,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
async with self._credential_client(credential) as client:
|
||||||
|
response = await client.delete(
|
||||||
|
"/api/v1/fs",
|
||||||
|
params={"uri": uri, "recursive": str(recursive).lower()},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
async def find(self, credential: OpenVikingCredential | str, query: str, limit: int) -> dict[str, Any]:
|
async def find(self, credential: OpenVikingCredential | str, query: str, limit: int) -> dict[str, Any]:
|
||||||
user_id = credential.user_id if isinstance(credential, OpenVikingCredential) else None
|
user_id = credential.user_id if isinstance(credential, OpenVikingCredential) else None
|
||||||
target_uri = f"viking://user/{user_id}/memories/" if user_id else "viking://user/memories/"
|
target_uri = f"viking://user/{user_id}/memories/" if user_id else "viking://user/memories/"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
|
|
||||||
OperationStatus = Literal["success", "partial_success", "failed"]
|
OperationStatus = Literal["success", "partial_success", "failed"]
|
||||||
|
MemoryWriteMode = Literal["create", "replace", "append"]
|
||||||
|
|
||||||
|
|
||||||
class MessageIngestRequest(BaseModel):
|
class MessageIngestRequest(BaseModel):
|
||||||
@ -54,6 +55,15 @@ class ProfileRequest(BaseModel):
|
|||||||
level: int = Field(default=2, ge=0)
|
level: int = Field(default=2, ge=0)
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryWriteRequest(BaseModel):
|
||||||
|
user_id: str = Field(min_length=1)
|
||||||
|
user_key: str = Field(min_length=1)
|
||||||
|
uri: str = Field(min_length=1)
|
||||||
|
content: str
|
||||||
|
mode: MemoryWriteMode = "create"
|
||||||
|
wait: bool = True
|
||||||
|
|
||||||
|
|
||||||
class ResourceUploadRequest(BaseModel):
|
class ResourceUploadRequest(BaseModel):
|
||||||
user_id: str = Field(min_length=1)
|
user_id: str = Field(min_length=1)
|
||||||
user_key: str = Field(min_length=1)
|
user_key: str = Field(min_length=1)
|
||||||
@ -116,6 +126,12 @@ class ProfileResponse(BaseModel):
|
|||||||
backends: dict[str, BackendStatus]
|
backends: dict[str, BackendStatus]
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryOperationResponse(BaseModel):
|
||||||
|
status: OperationStatus
|
||||||
|
memory: Any = None
|
||||||
|
backends: dict[str, BackendStatus]
|
||||||
|
|
||||||
|
|
||||||
class ResourceMutationResponse(BaseModel):
|
class ResourceMutationResponse(BaseModel):
|
||||||
status: OperationStatus
|
status: OperationStatus
|
||||||
resource: Any = None
|
resource: Any = None
|
||||||
|
|||||||
@ -11,6 +11,8 @@ from .schemas import (
|
|||||||
BackendStatus,
|
BackendStatus,
|
||||||
CommitResponse,
|
CommitResponse,
|
||||||
ExtractResponse,
|
ExtractResponse,
|
||||||
|
MemoryOperationResponse,
|
||||||
|
MemoryWriteRequest,
|
||||||
MessageIngestRequest,
|
MessageIngestRequest,
|
||||||
MessageIngestResponse,
|
MessageIngestResponse,
|
||||||
ProfileResponse,
|
ProfileResponse,
|
||||||
@ -76,6 +78,63 @@ class MemorySystemService:
|
|||||||
resource = backends["openviking"].result if backends["openviking"].status == "success" else None
|
resource = backends["openviking"].result if backends["openviking"].status == "success" else None
|
||||||
return ResourceMutationResponse(status=self._aggregate_status(backends), resource=resource, backends=backends)
|
return ResourceMutationResponse(status=self._aggregate_status(backends), resource=resource, backends=backends)
|
||||||
|
|
||||||
|
async def list_memories(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
user_key: str,
|
||||||
|
uri: str = "viking://user/memories",
|
||||||
|
recursive: bool = True,
|
||||||
|
) -> MemoryOperationResponse:
|
||||||
|
credential = self.openviking.credential_for_user(user_id, user_key)
|
||||||
|
backends = {
|
||||||
|
"openviking": await self._capture(lambda: self.openviking.list_memories(credential, uri, recursive)),
|
||||||
|
}
|
||||||
|
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
|
||||||
|
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
|
||||||
|
|
||||||
|
async def read_memory(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
user_key: str,
|
||||||
|
uri: str,
|
||||||
|
) -> MemoryOperationResponse:
|
||||||
|
credential = self.openviking.credential_for_user(user_id, user_key)
|
||||||
|
backends = {
|
||||||
|
"openviking": await self._capture(lambda: self.openviking.read_memory(credential, uri)),
|
||||||
|
}
|
||||||
|
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
|
||||||
|
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
|
||||||
|
|
||||||
|
async def write_memory(self, request: MemoryWriteRequest) -> MemoryOperationResponse:
|
||||||
|
credential = self.openviking.credential_for_user(request.user_id, request.user_key)
|
||||||
|
backends = {
|
||||||
|
"openviking": await self._capture(
|
||||||
|
lambda: self.openviking.write_memory(
|
||||||
|
credential,
|
||||||
|
uri=request.uri,
|
||||||
|
content=request.content,
|
||||||
|
mode=request.mode,
|
||||||
|
wait=request.wait,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
|
||||||
|
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
|
||||||
|
|
||||||
|
async def delete_memory(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
user_key: str,
|
||||||
|
uri: str,
|
||||||
|
recursive: bool = False,
|
||||||
|
) -> MemoryOperationResponse:
|
||||||
|
credential = self.openviking.credential_for_user(user_id, user_key)
|
||||||
|
backends = {
|
||||||
|
"openviking": await self._capture(lambda: self.openviking.delete_memory(credential, uri, recursive)),
|
||||||
|
}
|
||||||
|
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
|
||||||
|
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
|
||||||
|
|
||||||
async def ingest_messages(self, request: MessageIngestRequest) -> MessageIngestResponse:
|
async def ingest_messages(self, request: MessageIngestRequest) -> MessageIngestResponse:
|
||||||
messages = self._messages_from_request(request)
|
messages = self._messages_from_request(request)
|
||||||
if not messages:
|
if not messages:
|
||||||
|
|||||||
@ -61,6 +61,10 @@ Base path: `/memory-system`
|
|||||||
| `POST` | `/sessions/{session_id}/extract` | Trigger OpenViking extract only | Yes |
|
| `POST` | `/sessions/{session_id}/extract` | Trigger OpenViking extract only | Yes |
|
||||||
| `GET/POST` | `/sessions/{session_id}/context` | Read OpenViking session context plus EverOS recall | Yes |
|
| `GET/POST` | `/sessions/{session_id}/context` | Read OpenViking session context plus EverOS recall | Yes |
|
||||||
| `GET` | `/openviking/tasks/{task_id}` | Poll OpenViking task status | Yes |
|
| `GET` | `/openviking/tasks/{task_id}` | Poll OpenViking task status | Yes |
|
||||||
|
| `GET` | `/memories` | List OpenViking memory URIs under a memory root | Yes |
|
||||||
|
| `GET` | `/memories/content` | Read one OpenViking memory URI | Yes |
|
||||||
|
| `POST` | `/memories` | Create, replace, or append an OpenViking memory via `content/write` | Yes |
|
||||||
|
| `DELETE` | `/memories` | Delete an OpenViking memory URI via `fs` | Yes |
|
||||||
| `POST` | `/search` | Search OpenViking and EverOS | Yes |
|
| `POST` | `/search` | Search OpenViking and EverOS | Yes |
|
||||||
| `GET` | `/users/{user_id}/profile` | Read EverOS profile | Yes |
|
| `GET` | `/users/{user_id}/profile` | Read EverOS profile | Yes |
|
||||||
|
|
||||||
@ -147,6 +151,50 @@ GET with query parameters is also supported:
|
|||||||
curl -sS "$BASE/memory-system/sessions/sessionA1/context?user_id=userA&user_key=$USER_KEY&query=我喜欢喝什么?&limit=10"
|
curl -sS "$BASE/memory-system/sessions/sessionA1/context?user_id=userA&user_key=$USER_KEY&query=我喜欢喝什么?&limit=10"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
List memories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -G "$BASE/memory-system/memories" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories" \
|
||||||
|
--data-urlencode "recursive=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
Read memory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -G "$BASE/memory-system/memories/content" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories/preferences/python.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create, replace, or append memory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X POST "$BASE/memory-system/memories" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"user_id": "userA",
|
||||||
|
"user_key": "'"$USER_KEY"'",
|
||||||
|
"uri": "viking://user/memories/preferences/python.md",
|
||||||
|
"content": "# Python 偏好\n\n用户偏好使用 Python 做数据分析。",
|
||||||
|
"mode": "replace",
|
||||||
|
"wait": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete memory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X DELETE -G "$BASE/memory-system/memories" \
|
||||||
|
--data-urlencode "user_id=userA" \
|
||||||
|
--data-urlencode "user_key=$USER_KEY" \
|
||||||
|
--data-urlencode "uri=viking://user/memories/preferences/python.md" \
|
||||||
|
--data-urlencode "recursive=false"
|
||||||
|
```
|
||||||
|
|
||||||
## Response Handling
|
## Response Handling
|
||||||
|
|
||||||
Top-level `status` is one of:
|
Top-level `status` is one of:
|
||||||
|
|||||||
@ -68,8 +68,8 @@ class FakeAsyncClient:
|
|||||||
self.calls.append(("post", self.api_key, self.headers, path, json, files))
|
self.calls.append(("post", self.api_key, self.headers, path, json, files))
|
||||||
return self.responses.pop(0)
|
return self.responses.pop(0)
|
||||||
|
|
||||||
async def get(self, path: str) -> FakeResponse:
|
async def get(self, path: str, params: dict | None = None) -> FakeResponse:
|
||||||
self.calls.append(("get", self.api_key, self.headers, path, None))
|
self.calls.append(("get", self.api_key, self.headers, path, params))
|
||||||
return self.responses.pop(0)
|
return self.responses.pop(0)
|
||||||
|
|
||||||
async def delete(self, path: str, params: dict | None = None) -> FakeResponse:
|
async def delete(self, path: str, params: dict | None = None) -> FakeResponse:
|
||||||
@ -573,6 +573,135 @@ def test_openviking_delete_resource_sends_uri_and_recursive_flag():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_openviking_list_memories_calls_fs_ls_with_recursive_flag():
|
||||||
|
client = OpenVikingMemorySystemClient(store=FakeStore())
|
||||||
|
calls = []
|
||||||
|
responses = [FakeResponse(200, {"status": "ok", "result": {"children": []}})]
|
||||||
|
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
|
||||||
|
calls,
|
||||||
|
responses,
|
||||||
|
api_key,
|
||||||
|
extra_headers or {},
|
||||||
|
)
|
||||||
|
credential = client.user_credential("tom-key", "tom")
|
||||||
|
|
||||||
|
result = asyncio.run(
|
||||||
|
client.list_memories(
|
||||||
|
credential,
|
||||||
|
uri="viking://user/memories",
|
||||||
|
recursive=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {"status": "ok", "result": {"children": []}}
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"get",
|
||||||
|
"tom-key",
|
||||||
|
{},
|
||||||
|
"/api/v1/fs/ls",
|
||||||
|
{"uri": "viking://user/memories", "recursive": "true"},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_openviking_read_memory_calls_content_read():
|
||||||
|
client = OpenVikingMemorySystemClient(store=FakeStore())
|
||||||
|
calls = []
|
||||||
|
responses = [FakeResponse(200, {"status": "ok", "result": {"content": "# Python"}})]
|
||||||
|
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
|
||||||
|
calls,
|
||||||
|
responses,
|
||||||
|
api_key,
|
||||||
|
extra_headers or {},
|
||||||
|
)
|
||||||
|
credential = client.user_credential("tom-key", "tom")
|
||||||
|
|
||||||
|
result = asyncio.run(client.read_memory(credential, "viking://user/memories/preferences/python.md"))
|
||||||
|
|
||||||
|
assert result == {"status": "ok", "result": {"content": "# Python"}}
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"get",
|
||||||
|
"tom-key",
|
||||||
|
{},
|
||||||
|
"/api/v1/content/read",
|
||||||
|
{"uri": "viking://user/memories/preferences/python.md"},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_openviking_write_memory_posts_content_write_mode_and_wait():
|
||||||
|
client = OpenVikingMemorySystemClient(store=FakeStore())
|
||||||
|
calls = []
|
||||||
|
responses = [FakeResponse(200, {"status": "ok", "result": {"uri": "viking://user/memories/profile.md"}})]
|
||||||
|
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
|
||||||
|
calls,
|
||||||
|
responses,
|
||||||
|
api_key,
|
||||||
|
extra_headers or {},
|
||||||
|
)
|
||||||
|
credential = client.user_credential("tom-key", "tom")
|
||||||
|
|
||||||
|
result = asyncio.run(
|
||||||
|
client.write_memory(
|
||||||
|
credential,
|
||||||
|
uri="viking://user/memories/profile.md",
|
||||||
|
content="# Profile\n\nLikes Python.",
|
||||||
|
mode="replace",
|
||||||
|
wait=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md"}}
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"post",
|
||||||
|
"tom-key",
|
||||||
|
{},
|
||||||
|
"/api/v1/content/write",
|
||||||
|
{
|
||||||
|
"uri": "viking://user/memories/profile.md",
|
||||||
|
"content": "# Profile\n\nLikes Python.",
|
||||||
|
"mode": "replace",
|
||||||
|
"wait": True,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_openviking_delete_memory_defaults_non_recursive():
|
||||||
|
client = OpenVikingMemorySystemClient(store=FakeStore())
|
||||||
|
calls = []
|
||||||
|
responses = [FakeResponse(200, {"status": "ok", "result": {"estimated_deleted_count": 1}})]
|
||||||
|
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
|
||||||
|
calls,
|
||||||
|
responses,
|
||||||
|
api_key,
|
||||||
|
extra_headers or {},
|
||||||
|
)
|
||||||
|
credential = client.user_credential("tom-key", "tom")
|
||||||
|
|
||||||
|
result = asyncio.run(
|
||||||
|
client.delete_memory(
|
||||||
|
credential,
|
||||||
|
uri="viking://user/memories/preferences/python.md",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {"status": "ok", "result": {"estimated_deleted_count": 1}}
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"delete",
|
||||||
|
"tom-key",
|
||||||
|
{},
|
||||||
|
"/api/v1/fs",
|
||||||
|
{"uri": "viking://user/memories/preferences/python.md", "recursive": "false"},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_everos_assistant_payload_does_not_use_user_id_as_sender():
|
def test_everos_assistant_payload_does_not_use_user_id_as_sender():
|
||||||
client = EverOSMemorySystemClient()
|
client = EverOSMemorySystemClient()
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,8 @@ def test_memory_system_server_exposes_routes():
|
|||||||
assert {"GET", "POST"} <= context_methods
|
assert {"GET", "POST"} <= context_methods
|
||||||
assert "/memory-system/search" in paths
|
assert "/memory-system/search" in paths
|
||||||
assert "/memory-system/resources" in paths
|
assert "/memory-system/resources" in paths
|
||||||
|
assert "/memory-system/memories" in paths
|
||||||
|
assert "/memory-system/memories/content" in paths
|
||||||
assert "/memory-system/users/{user_id}/profile" in paths
|
assert "/memory-system/users/{user_id}/profile" in paths
|
||||||
task_methods = {
|
task_methods = {
|
||||||
method
|
method
|
||||||
@ -36,6 +38,20 @@ def test_memory_system_server_exposes_routes():
|
|||||||
for method in getattr(route, "methods", set())
|
for method in getattr(route, "methods", set())
|
||||||
}
|
}
|
||||||
assert {"DELETE", "POST"} <= resource_methods
|
assert {"DELETE", "POST"} <= resource_methods
|
||||||
|
memory_methods = {
|
||||||
|
method
|
||||||
|
for route in app.routes
|
||||||
|
if getattr(route, "path", "") == "/memory-system/memories"
|
||||||
|
for method in getattr(route, "methods", set())
|
||||||
|
}
|
||||||
|
memory_content_methods = {
|
||||||
|
method
|
||||||
|
for route in app.routes
|
||||||
|
if getattr(route, "path", "") == "/memory-system/memories/content"
|
||||||
|
for method in getattr(route, "methods", set())
|
||||||
|
}
|
||||||
|
assert {"DELETE", "GET", "POST"} <= memory_methods
|
||||||
|
assert {"GET"} <= memory_content_methods
|
||||||
|
|
||||||
|
|
||||||
def test_memory_system_messages_does_not_require_account_key_header():
|
def test_memory_system_messages_does_not_require_account_key_header():
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from memory_system_api.schemas import MessageIngestRequest, ResourceUploadRequest, SearchRequest, SessionContextRequest
|
from memory_system_api.schemas import (
|
||||||
|
MemoryWriteRequest,
|
||||||
|
MessageIngestRequest,
|
||||||
|
ResourceUploadRequest,
|
||||||
|
SearchRequest,
|
||||||
|
SessionContextRequest,
|
||||||
|
)
|
||||||
from memory_system_api.service import MemorySystemService
|
from memory_system_api.service import MemorySystemService
|
||||||
|
|
||||||
|
|
||||||
@ -120,6 +126,22 @@ class FakeOpenViking:
|
|||||||
self.calls.append(("delete_resource", user_key, uri, recursive))
|
self.calls.append(("delete_resource", user_key, uri, recursive))
|
||||||
return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 4}}
|
return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 4}}
|
||||||
|
|
||||||
|
async def list_memories(self, user_key: str, uri: str, recursive: bool = True) -> dict:
|
||||||
|
self.calls.append(("list_memories", user_key, uri, recursive))
|
||||||
|
return {"status": "ok", "result": {"children": [{"uri": "viking://user/memories/profile.md"}]}}
|
||||||
|
|
||||||
|
async def read_memory(self, user_key: str, uri: str) -> dict:
|
||||||
|
self.calls.append(("read_memory", user_key, uri))
|
||||||
|
return {"status": "ok", "result": {"uri": uri, "content": "# Profile"}}
|
||||||
|
|
||||||
|
async def write_memory(self, user_key: str, uri: str, content: str, mode: str, wait: bool = True) -> dict:
|
||||||
|
self.calls.append(("write_memory", user_key, uri, content, mode, wait))
|
||||||
|
return {"status": "ok", "result": {"uri": uri, "mode": mode}}
|
||||||
|
|
||||||
|
async def delete_memory(self, user_key: str, uri: str, recursive: bool = False) -> dict:
|
||||||
|
self.calls.append(("delete_memory", user_key, uri, recursive))
|
||||||
|
return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 1}}
|
||||||
|
|
||||||
|
|
||||||
class FakeEverOS:
|
class FakeEverOS:
|
||||||
def __init__(self, fail_on_append: bool = False):
|
def __init__(self, fail_on_append: bool = False):
|
||||||
@ -334,6 +356,93 @@ def test_delete_resource_delegates_to_openviking_only():
|
|||||||
assert everos.calls == []
|
assert everos.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_memories_delegates_to_openviking_only():
|
||||||
|
openviking = FakeOpenViking()
|
||||||
|
everos = FakeEverOS()
|
||||||
|
service = MemorySystemService(openviking=openviking, everos=everos)
|
||||||
|
|
||||||
|
response = asyncio.run(service.list_memories(
|
||||||
|
user_id="tom",
|
||||||
|
user_key="tom-key",
|
||||||
|
uri="viking://user/memories",
|
||||||
|
recursive=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status == "success"
|
||||||
|
assert response.memory == {"status": "ok", "result": {"children": [{"uri": "viking://user/memories/profile.md"}]}}
|
||||||
|
assert openviking.calls == [
|
||||||
|
("credential_for_user", "tom", "tom-key", None),
|
||||||
|
("list_memories", "key-tom", "viking://user/memories", True),
|
||||||
|
]
|
||||||
|
assert everos.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_memory_delegates_to_openviking_only():
|
||||||
|
openviking = FakeOpenViking()
|
||||||
|
everos = FakeEverOS()
|
||||||
|
service = MemorySystemService(openviking=openviking, everos=everos)
|
||||||
|
|
||||||
|
response = asyncio.run(service.read_memory(
|
||||||
|
user_id="tom",
|
||||||
|
user_key="tom-key",
|
||||||
|
uri="viking://user/memories/profile.md",
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status == "success"
|
||||||
|
assert response.memory == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md", "content": "# Profile"}}
|
||||||
|
assert openviking.calls == [
|
||||||
|
("credential_for_user", "tom", "tom-key", None),
|
||||||
|
("read_memory", "key-tom", "viking://user/memories/profile.md"),
|
||||||
|
]
|
||||||
|
assert everos.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_memory_delegates_to_openviking_content_write_only():
|
||||||
|
openviking = FakeOpenViking()
|
||||||
|
everos = FakeEverOS()
|
||||||
|
service = MemorySystemService(openviking=openviking, everos=everos)
|
||||||
|
|
||||||
|
response = asyncio.run(service.write_memory(MemoryWriteRequest(
|
||||||
|
user_id="tom",
|
||||||
|
user_key="tom-key",
|
||||||
|
uri="viking://user/memories/profile.md",
|
||||||
|
content="# Profile\n\nLikes Python.",
|
||||||
|
mode="replace",
|
||||||
|
wait=True,
|
||||||
|
)))
|
||||||
|
|
||||||
|
assert response.status == "success"
|
||||||
|
assert response.memory == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md", "mode": "replace"}}
|
||||||
|
assert openviking.calls == [
|
||||||
|
("credential_for_user", "tom", "tom-key", None),
|
||||||
|
("write_memory", "key-tom", "viking://user/memories/profile.md", "# Profile\n\nLikes Python.", "replace", True),
|
||||||
|
]
|
||||||
|
assert everos.calls == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_memory_delegates_to_openviking_only_and_defaults_non_recursive():
|
||||||
|
openviking = FakeOpenViking()
|
||||||
|
everos = FakeEverOS()
|
||||||
|
service = MemorySystemService(openviking=openviking, everos=everos)
|
||||||
|
|
||||||
|
response = asyncio.run(service.delete_memory(
|
||||||
|
user_id="tom",
|
||||||
|
user_key="tom-key",
|
||||||
|
uri="viking://user/memories/preferences/python.md",
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status == "success"
|
||||||
|
assert response.memory == {
|
||||||
|
"status": "ok",
|
||||||
|
"result": {"uri": "viking://user/memories/preferences/python.md", "estimated_deleted_count": 1},
|
||||||
|
}
|
||||||
|
assert openviking.calls == [
|
||||||
|
("credential_for_user", "tom", "tom-key", None),
|
||||||
|
("delete_memory", "key-tom", "viking://user/memories/preferences/python.md", False),
|
||||||
|
]
|
||||||
|
assert everos.calls == []
|
||||||
|
|
||||||
|
|
||||||
def test_search_removes_vectors_from_items_and_backend_results():
|
def test_search_removes_vectors_from_items_and_backend_results():
|
||||||
service = MemorySystemService(openviking=FakeOpenViking(), everos=FakeEverOSWithVector())
|
service = MemorySystemService(openviking=FakeOpenViking(), everos=FakeEverOSWithVector())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user