lisa-pack-mem0 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lisa_pack_mem0-0.1.0/PKG-INFO +9 -0
- lisa_pack_mem0-0.1.0/README.md +64 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/PKG-INFO +9 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/SOURCES.txt +15 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/dependency_links.txt +1 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/entry_points.txt +2 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/requires.txt +5 -0
- lisa_pack_mem0-0.1.0/lisa_pack_mem0.egg-info/top_level.txt +1 -0
- lisa_pack_mem0-0.1.0/lisa_packs/__init__.py +5 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/__init__.py +10 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/mcp_servers.yaml +40 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/result_maps.py +147 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/tests/__init__.py +0 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/tests/test_manifest.py +20 -0
- lisa_pack_mem0-0.1.0/lisa_packs/mem0/tests/test_result_maps.py +147 -0
- lisa_pack_mem0-0.1.0/pyproject.toml +36 -0
- lisa_pack_mem0-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lisa-pack-mem0
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lisa Mem0 provider pack - external memory backend and MCP server manifest
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
8
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
9
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# lisa-pack-mem0
|
|
2
|
+
|
|
3
|
+
Lisa Mem0 provider pack — external memory backend and MCP server manifest.
|
|
4
|
+
|
|
5
|
+
The pack owns the generic `memory.add` / `memory.search` / `memory.list`
|
|
6
|
+
aliases and routes them to Mem0, so any Lisa operation that reads or writes
|
|
7
|
+
long-term memory works against Mem0 without changing.
|
|
8
|
+
|
|
9
|
+
## What it provides
|
|
10
|
+
|
|
11
|
+
| Layer | Path | Role |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| MCP manifest | `mcp_servers.yaml` | Declares the Mem0 backend, its result maps, and the tool policy. |
|
|
14
|
+
| Result maps | `result_maps.py` | Normalize Mem0 responses to canonical Lisa `MemoryRecord` JSON. |
|
|
15
|
+
|
|
16
|
+
### Result maps
|
|
17
|
+
|
|
18
|
+
- **`mem0_add_result_map`** — normalizes the add response to a confirmation
|
|
19
|
+
string.
|
|
20
|
+
- **`mem0_search_result_map`** — maps the Mem0 `score` field to `salience`.
|
|
21
|
+
- **`mem0_list_result_map`** — unwraps the paginated envelope into a flat
|
|
22
|
+
JSON array of records.
|
|
23
|
+
|
|
24
|
+
### Tool policy
|
|
25
|
+
|
|
26
|
+
The manifest denies `delete_all_memories`, `delete_entitites`, and
|
|
27
|
+
`delete_memory`, so a model cannot erase the user's memory through the pack.
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
| Variable | Meaning |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `MEM0_API_KEY` | Bearer token for the Mem0 MCP server. |
|
|
34
|
+
|
|
35
|
+
Select the pack as the memory owner via session context:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
providers:
|
|
39
|
+
capabilities:
|
|
40
|
+
memory: lisa-pack-mem0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install lisa-pack-mem0
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run ruff format --check lisa_packs/mem0
|
|
53
|
+
uv run ruff check lisa_packs/mem0
|
|
54
|
+
uv run mypy lisa_packs/mem0
|
|
55
|
+
uv run pytest -m "not live"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run the checks from a sibling framework checkout so the shared
|
|
59
|
+
`[tool.ruff]` and `[tool.mypy]` settings apply; `pytest` runs from the pack
|
|
60
|
+
root so `pythonpath = ["."]` makes `lisa_packs.mem0` importable.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lisa-pack-mem0
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lisa Mem0 provider pack - external memory backend and MCP server manifest
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
8
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
9
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
lisa_pack_mem0.egg-info/PKG-INFO
|
|
4
|
+
lisa_pack_mem0.egg-info/SOURCES.txt
|
|
5
|
+
lisa_pack_mem0.egg-info/dependency_links.txt
|
|
6
|
+
lisa_pack_mem0.egg-info/entry_points.txt
|
|
7
|
+
lisa_pack_mem0.egg-info/requires.txt
|
|
8
|
+
lisa_pack_mem0.egg-info/top_level.txt
|
|
9
|
+
lisa_packs/__init__.py
|
|
10
|
+
lisa_packs/mem0/__init__.py
|
|
11
|
+
lisa_packs/mem0/mcp_servers.yaml
|
|
12
|
+
lisa_packs/mem0/result_maps.py
|
|
13
|
+
lisa_packs/mem0/tests/__init__.py
|
|
14
|
+
lisa_packs/mem0/tests/test_manifest.py
|
|
15
|
+
lisa_packs/mem0/tests/test_result_maps.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lisa_packs
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1
|
|
3
|
+
|
|
4
|
+
# External Mem0 provider for memory capability ownership.
|
|
5
|
+
# When selected by host policy, this pack owns memory aliases and routes
|
|
6
|
+
# memory.add/search/list through Mem0.
|
|
7
|
+
memory:
|
|
8
|
+
default_backend: mem0
|
|
9
|
+
backends:
|
|
10
|
+
- id: mem0
|
|
11
|
+
kind: http
|
|
12
|
+
url: https://mcp.mem0.ai/mcp
|
|
13
|
+
headers:
|
|
14
|
+
Authorization: "Bearer ${MEM0_API_KEY}"
|
|
15
|
+
agent_id: lisa
|
|
16
|
+
tool_names:
|
|
17
|
+
add: add_memory
|
|
18
|
+
search: search_memories
|
|
19
|
+
list: get_memories
|
|
20
|
+
result_maps:
|
|
21
|
+
add: lisa_packs.mem0.result_maps.mem0_add_result_map
|
|
22
|
+
search: lisa_packs.mem0.result_maps.mem0_search_result_map
|
|
23
|
+
list: lisa_packs.mem0.result_maps.mem0_list_result_map
|
|
24
|
+
|
|
25
|
+
servers:
|
|
26
|
+
- server_id: mem0
|
|
27
|
+
runtime_kind: http
|
|
28
|
+
url: https://mcp.mem0.ai/mcp
|
|
29
|
+
auth:
|
|
30
|
+
type: bearer
|
|
31
|
+
token: "${MEM0_API_KEY}"
|
|
32
|
+
tool_policy:
|
|
33
|
+
denied_tools:
|
|
34
|
+
- delete_all_memories
|
|
35
|
+
- delete_entitites
|
|
36
|
+
- delete_memory
|
|
37
|
+
proxy_policy:
|
|
38
|
+
ambient:
|
|
39
|
+
user_id: user_slug
|
|
40
|
+
structured_filters: true
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Output-normalization adapters for Mem0 MCP tool responses.
|
|
2
|
+
|
|
3
|
+
These functions are declared in ``mcp_servers.yaml`` under
|
|
4
|
+
``memory.backends[].result_maps`` and resolved by
|
|
5
|
+
:func:`hosts.core.memory_aliases._backend_result_maps` at alias-registration
|
|
6
|
+
time. Each one receives the raw ``output_text`` from the Mem0 MCP tool and
|
|
7
|
+
returns a normalized string in the **canonical memory shape** — a JSON array
|
|
8
|
+
of ``MemoryRecord``-compatible dicts — so the learning-pack operations
|
|
9
|
+
(``recall_memory``, ``synthesize_and_store_learning``) consume a uniform
|
|
10
|
+
shape regardless of which backend is active.
|
|
11
|
+
|
|
12
|
+
Mem0 response shapes (v3):
|
|
13
|
+
- ``get_memories`` (list): ``{"count": N, "next": ..., "previous": ..., "results": [...]}``
|
|
14
|
+
- ``search_memories`` (search): ``{"results": [...]}`` — items include a ``score`` field
|
|
15
|
+
- ``add_memory`` (add): ``{"results": [{"id": ..., "memory": ..., "event_id": ...}]}``
|
|
16
|
+
|
|
17
|
+
Each result item has:
|
|
18
|
+
- ``id``: UUID string (not int)
|
|
19
|
+
- ``memory``: the memory text (not ``content``)
|
|
20
|
+
- ``created_at``: ISO datetime string
|
|
21
|
+
- ``score`` (search only): float 0..1
|
|
22
|
+
- ``user_id``: the scoped user
|
|
23
|
+
- ``metadata``: dict
|
|
24
|
+
- ``categories``: list[str]
|
|
25
|
+
- ``updated_at``: ISO datetime or null
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import logging
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _unwrap_results(raw_text: str) -> list[dict[str, Any]]:
|
|
38
|
+
"""Parse the raw MCP tool output and extract the results list.
|
|
39
|
+
|
|
40
|
+
Handles three shapes:
|
|
41
|
+
1. Mem0 paginated envelope: ``{"results": [...], "count": ...}``
|
|
42
|
+
2. Bare list of items (already canonical from some other backend)
|
|
43
|
+
3. Empty or invalid → returns ``[]``
|
|
44
|
+
"""
|
|
45
|
+
if not raw_text or not raw_text.strip():
|
|
46
|
+
return []
|
|
47
|
+
try:
|
|
48
|
+
data: Any = json.loads(raw_text)
|
|
49
|
+
except (json.JSONDecodeError, TypeError):
|
|
50
|
+
logger.debug("result_map: failed to parse JSON, returning empty list")
|
|
51
|
+
return []
|
|
52
|
+
if isinstance(data, list):
|
|
53
|
+
return [item for item in data if isinstance(item, dict)]
|
|
54
|
+
if isinstance(data, dict):
|
|
55
|
+
results = data.get("results")
|
|
56
|
+
if isinstance(results, list):
|
|
57
|
+
return [item for item in results if isinstance(item, dict)]
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _mem0_item_to_record(item: dict[str, Any]) -> dict[str, Any]:
|
|
62
|
+
"""Map one Mem0 result item to a canonical ``MemoryRecord``-compatible dict.
|
|
63
|
+
|
|
64
|
+
Mem0 fields → canonical fields:
|
|
65
|
+
id (UUID str) → id (kept as str; MemoryRecord.id accepts int|str)
|
|
66
|
+
memory → content
|
|
67
|
+
created_at → created_at
|
|
68
|
+
user_id → user_slug
|
|
69
|
+
score → salience (search responses only)
|
|
70
|
+
metadata → content_json (merged as-is)
|
|
71
|
+
categories → stored in content_json for preservation
|
|
72
|
+
"""
|
|
73
|
+
content_json: dict[str, Any] = {}
|
|
74
|
+
metadata = item.get("metadata")
|
|
75
|
+
if isinstance(metadata, dict):
|
|
76
|
+
content_json["metadata"] = metadata
|
|
77
|
+
categories = item.get("categories")
|
|
78
|
+
if isinstance(categories, list):
|
|
79
|
+
content_json["categories"] = categories
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"id": str(item.get("id", "")),
|
|
83
|
+
"content": str(item.get("memory", "")),
|
|
84
|
+
"created_at": str(item.get("created_at", "")),
|
|
85
|
+
"user_slug": str(item.get("user_id", "")),
|
|
86
|
+
"user_id": str(item.get("user_id", "")),
|
|
87
|
+
"salience": float(item.get("score", 0.0) or 0.0),
|
|
88
|
+
"content_json": content_json,
|
|
89
|
+
"memory_scope": "personal",
|
|
90
|
+
"memory_tier": "long_term",
|
|
91
|
+
"surface_id": "",
|
|
92
|
+
"source_user_slug": str(item.get("user_id", "")),
|
|
93
|
+
"operation_id": "mem0.add_memory",
|
|
94
|
+
"agent_id": "lisa",
|
|
95
|
+
"workspace_id": "default",
|
|
96
|
+
"memory_kind": "episodic",
|
|
97
|
+
"confidence": 1.0,
|
|
98
|
+
"evidence_ids": [],
|
|
99
|
+
"supersedes_id": None,
|
|
100
|
+
"valid_until": None,
|
|
101
|
+
"last_accessed_at": None,
|
|
102
|
+
"access_count": 0,
|
|
103
|
+
"status": "active",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def mem0_list_result_map(raw_text: str) -> str:
|
|
108
|
+
"""Normalize ``get_memories`` output into a canonical JSON array of records.
|
|
109
|
+
|
|
110
|
+
Mem0's ``get_memories`` returns a paginated envelope:
|
|
111
|
+
``{"count": N, "next": ..., "previous": ..., "results": [...]}``
|
|
112
|
+
|
|
113
|
+
This unwraps ``results``, maps each item to the ``MemoryRecord`` shape,
|
|
114
|
+
and returns a flat JSON array string.
|
|
115
|
+
"""
|
|
116
|
+
items = _unwrap_results(raw_text)
|
|
117
|
+
records = [_mem0_item_to_record(item) for item in items]
|
|
118
|
+
return json.dumps(records, default=str)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def mem0_search_result_map(raw_text: str) -> str:
|
|
122
|
+
"""Normalize ``search_memories`` output into a canonical JSON array of records.
|
|
123
|
+
|
|
124
|
+
Same envelope as ``get_memories`` (``{"results": [...]}``), but each
|
|
125
|
+
search item includes a ``score`` field (float 0..1) that maps to
|
|
126
|
+
``salience`` in the canonical record.
|
|
127
|
+
"""
|
|
128
|
+
items = _unwrap_results(raw_text)
|
|
129
|
+
records = [_mem0_item_to_record(item) for item in items]
|
|
130
|
+
return json.dumps(records, default=str)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def mem0_add_result_map(raw_text: str) -> str:
|
|
134
|
+
"""Normalize ``add_memory`` output into a simple confirmation string.
|
|
135
|
+
|
|
136
|
+
Mem0's ``add_memory`` returns ``{"results": [{"id": ..., "memory": ..., "event_id": ...}]}``
|
|
137
|
+
(an async job accepted response). Normalize to a simple confirmation
|
|
138
|
+
so operations checking ``tool_result.error`` see a clean success.
|
|
139
|
+
"""
|
|
140
|
+
items = _unwrap_results(raw_text)
|
|
141
|
+
if not items:
|
|
142
|
+
return "Memory stored."
|
|
143
|
+
first = items[0]
|
|
144
|
+
memory_id = first.get("id", "")
|
|
145
|
+
if memory_id:
|
|
146
|
+
return f"Memory stored with ID {memory_id}"
|
|
147
|
+
return "Memory stored."
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from kernel.mcp_launch.server_manifest import load_mcp_server_manifest
|
|
5
|
+
|
|
6
|
+
from lisa_packs.mem0 import get_content_root
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_mem0_pack_manifest_loads_and_owns_memory(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
10
|
+
# The manifest expands ${MEM0_API_KEY} at load time and the manifest
|
|
11
|
+
# loader rejects an empty bearer token, so a value must be present even
|
|
12
|
+
# though this test never contacts the server.
|
|
13
|
+
monkeypatch.setenv("MEM0_API_KEY", "test-key")
|
|
14
|
+
|
|
15
|
+
root = get_content_root()
|
|
16
|
+
specs, memory = load_mcp_server_manifest(root / "mcp_servers.yaml")
|
|
17
|
+
|
|
18
|
+
assert memory.default_backend == "mem0"
|
|
19
|
+
assert any(backend.id == "mem0" for backend in memory.backends)
|
|
20
|
+
assert any(spec.server_id == "mem0" for spec in specs)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Tests for lisa_packs.mem0.result_maps — Mem0 response normalization adapters.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- mem0_list_result_map unwraps the paginated envelope into a flat JSON array.
|
|
5
|
+
- mem0_search_result_map maps score → salience.
|
|
6
|
+
- mem0_add_result_map normalizes the add response to a confirmation string.
|
|
7
|
+
- Edge cases: empty input, bare list, invalid JSON, missing fields.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from lisa_packs.mem0.result_maps import (
|
|
15
|
+
mem0_add_result_map,
|
|
16
|
+
mem0_list_result_map,
|
|
17
|
+
mem0_search_result_map,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_EXPECTED_RESULT_COUNT = 2
|
|
21
|
+
_EXPECTED_SEARCH_SALIENCE = 0.82
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Realistic Mem0 response payloads ──────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_MEM0_LIST_RESPONSE = json.dumps(
|
|
28
|
+
{
|
|
29
|
+
"count": 2,
|
|
30
|
+
"next": None,
|
|
31
|
+
"previous": None,
|
|
32
|
+
"results": [
|
|
33
|
+
{
|
|
34
|
+
"id": "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c",
|
|
35
|
+
"memory": "User moved to San Francisco from New York in January 2026",
|
|
36
|
+
"metadata": {"category": "location"},
|
|
37
|
+
"categories": ["location"],
|
|
38
|
+
"created_at": "2026-01-15T10:30:00Z",
|
|
39
|
+
"updated_at": "2026-01-15T10:30:00Z",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "a2b8c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
|
|
43
|
+
"memory": "Alex prefers vegetarian restaurants",
|
|
44
|
+
"metadata": {},
|
|
45
|
+
"categories": [],
|
|
46
|
+
"created_at": "2026-07-05T15:30:00Z",
|
|
47
|
+
"updated_at": "2026-07-05T15:30:00Z",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_MEM0_SEARCH_RESPONSE = json.dumps(
|
|
54
|
+
{
|
|
55
|
+
"results": [
|
|
56
|
+
{
|
|
57
|
+
"id": "ea925981-272f-40dd-b576-be64e4871429",
|
|
58
|
+
"memory": "Likes to play cricket and plays cricket on weekends.",
|
|
59
|
+
"user_id": "alice",
|
|
60
|
+
"metadata": {"category": "hobbies"},
|
|
61
|
+
"categories": ["hobbies"],
|
|
62
|
+
"score": 0.82,
|
|
63
|
+
"expiration_date": None,
|
|
64
|
+
"created_at": "2024-07-26T10:29:36.630547-07:00",
|
|
65
|
+
"updated_at": None,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_MEM0_ADD_RESPONSE = json.dumps(
|
|
72
|
+
{
|
|
73
|
+
"results": [
|
|
74
|
+
{
|
|
75
|
+
"id": "new-mem-uuid-1234",
|
|
76
|
+
"memory": "The user likes hiking",
|
|
77
|
+
"event_id": "evt-5678",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ── Tests ─────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_mem0_list_result_map_unwraps_envelope() -> None:
|
|
88
|
+
"""The paginated dict envelope is unwrapped to a flat JSON array."""
|
|
89
|
+
raw = mem0_list_result_map(_MEM0_LIST_RESPONSE)
|
|
90
|
+
data = json.loads(raw)
|
|
91
|
+
assert isinstance(data, list)
|
|
92
|
+
assert len(data) == _EXPECTED_RESULT_COUNT
|
|
93
|
+
assert data[0]["content"] == "User moved to San Francisco from New York in January 2026"
|
|
94
|
+
assert data[0]["id"] == "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c"
|
|
95
|
+
assert data[0]["created_at"] == "2026-01-15T10:30:00Z"
|
|
96
|
+
# metadata preserved in content_json
|
|
97
|
+
assert data[0]["content_json"]["metadata"] == {"category": "location"}
|
|
98
|
+
assert data[0]["content_json"]["categories"] == ["location"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_mem0_search_result_map_maps_score_to_salience() -> None:
|
|
102
|
+
"""The search score field maps to salience in the canonical record."""
|
|
103
|
+
raw = mem0_search_result_map(_MEM0_SEARCH_RESPONSE)
|
|
104
|
+
data = json.loads(raw)
|
|
105
|
+
assert isinstance(data, list)
|
|
106
|
+
assert len(data) == 1
|
|
107
|
+
assert data[0]["content"] == "Likes to play cricket and plays cricket on weekends."
|
|
108
|
+
assert data[0]["salience"] == _EXPECTED_SEARCH_SALIENCE
|
|
109
|
+
assert data[0]["user_slug"] == "alice"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_mem0_add_result_map_returns_confirmation() -> None:
|
|
113
|
+
"""The add response normalizes to a simple confirmation string."""
|
|
114
|
+
raw = mem0_add_result_map(_MEM0_ADD_RESPONSE)
|
|
115
|
+
assert raw == "Memory stored with ID new-mem-uuid-1234"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_mem0_list_result_map_empty_input() -> None:
|
|
119
|
+
"""Empty input returns an empty JSON array."""
|
|
120
|
+
assert json.loads(mem0_list_result_map("")) == []
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_mem0_list_result_map_bare_list() -> None:
|
|
124
|
+
"""A bare list (already canonical) passes through after normalization."""
|
|
125
|
+
bare = json.dumps([{"id": 1, "memory": "test", "created_at": "2026-01-01"}])
|
|
126
|
+
data = json.loads(mem0_list_result_map(bare))
|
|
127
|
+
assert isinstance(data, list)
|
|
128
|
+
assert len(data) == 1
|
|
129
|
+
assert data[0]["content"] == "test"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_mem0_list_result_map_invalid_json() -> None:
|
|
133
|
+
"""Invalid JSON returns an empty array."""
|
|
134
|
+
assert json.loads(mem0_list_result_map("not json")) == []
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_mem0_search_result_map_missing_score() -> None:
|
|
138
|
+
"""Items without a score field default salience to 0.0."""
|
|
139
|
+
raw = json.dumps({"results": [{"id": "x", "memory": "no score", "created_at": "2026-01-01"}]})
|
|
140
|
+
data = json.loads(mem0_search_result_map(raw))
|
|
141
|
+
assert data[0]["salience"] == 0.0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_mem0_add_result_map_empty() -> None:
|
|
145
|
+
"""Empty add response returns a generic confirmation."""
|
|
146
|
+
assert mem0_add_result_map("") == "Memory stored."
|
|
147
|
+
assert mem0_add_result_map("{}") == "Memory stored."
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools.packages.find]
|
|
6
|
+
where = ["."]
|
|
7
|
+
include = ["lisa_packs*", "lisa_packs.mem0*"]
|
|
8
|
+
|
|
9
|
+
[tool.setuptools.package-data]
|
|
10
|
+
"lisa_packs" = ["*"]
|
|
11
|
+
"lisa_packs.mem0" = ["mcp_servers.yaml"]
|
|
12
|
+
|
|
13
|
+
[project]
|
|
14
|
+
name = "lisa-pack-mem0"
|
|
15
|
+
version = "0.1.0"
|
|
16
|
+
description = "Lisa Mem0 provider pack - external memory backend and MCP server manifest"
|
|
17
|
+
requires-python = ">=3.11"
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=8.0.0",
|
|
22
|
+
"pytest-asyncio>=0.23.0",
|
|
23
|
+
"pytest-mock>=3.14.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
pythonpath = ["."]
|
|
28
|
+
testpaths = ["lisa_packs/mem0/tests"]
|
|
29
|
+
markers = [
|
|
30
|
+
"live: hits real external signal or provider APIs and prints a tester-visible summary",
|
|
31
|
+
"integration: multi-module in-process test",
|
|
32
|
+
"eval: deterministic agent regression evals (no model calls, no I/O)",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.entry-points."lisa.extensions.content_roots"]
|
|
36
|
+
mem0 = "lisa_packs.mem0:get_content_root"
|