MemoryOS 1.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

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.

Potentially problematic release.


This version of MemoryOS might be problematic. Click here for more details.

Files changed (42) hide show
  1. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/METADATA +2 -1
  2. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/RECORD +42 -33
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +25 -0
  5. memos/api/context/context_thread.py +96 -0
  6. memos/api/context/dependencies.py +0 -11
  7. memos/api/middleware/request_context.py +94 -0
  8. memos/api/product_api.py +5 -1
  9. memos/api/product_models.py +16 -0
  10. memos/api/routers/product_router.py +39 -3
  11. memos/api/start_api.py +3 -0
  12. memos/configs/memory.py +13 -0
  13. memos/configs/reranker.py +18 -0
  14. memos/graph_dbs/base.py +4 -2
  15. memos/graph_dbs/nebular.py +215 -68
  16. memos/graph_dbs/neo4j.py +14 -12
  17. memos/graph_dbs/neo4j_community.py +6 -3
  18. memos/llms/vllm.py +2 -0
  19. memos/log.py +120 -8
  20. memos/mem_os/core.py +30 -2
  21. memos/mem_os/product.py +386 -146
  22. memos/mem_os/utils/reference_utils.py +20 -0
  23. memos/mem_reader/simple_struct.py +112 -43
  24. memos/mem_user/mysql_user_manager.py +4 -2
  25. memos/memories/textual/item.py +1 -1
  26. memos/memories/textual/tree.py +31 -1
  27. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +3 -1
  28. memos/memories/textual/tree_text_memory/retrieve/recall.py +53 -3
  29. memos/memories/textual/tree_text_memory/retrieve/searcher.py +74 -14
  30. memos/memories/textual/tree_text_memory/retrieve/utils.py +6 -4
  31. memos/memos_tools/notification_utils.py +46 -0
  32. memos/reranker/__init__.py +4 -0
  33. memos/reranker/base.py +24 -0
  34. memos/reranker/cosine_local.py +95 -0
  35. memos/reranker/factory.py +43 -0
  36. memos/reranker/http_bge.py +99 -0
  37. memos/reranker/noop.py +16 -0
  38. memos/templates/mem_reader_prompts.py +289 -40
  39. memos/templates/mos_prompts.py +133 -60
  40. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
  41. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
  42. {memoryos-1.0.0.dist-info → memoryos-1.0.1.dist-info}/entry_points.txt +0 -0
memos/reranker/base.py ADDED
@@ -0,0 +1,24 @@
1
+ # memos/reranker/base.py
2
+ from __future__ import annotations
3
+
4
+ from abc import ABC, abstractmethod
5
+ from typing import TYPE_CHECKING
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from memos.memories.textual.item import TextualMemoryItem
10
+
11
+
12
+ class BaseReranker(ABC):
13
+ """Abstract interface for memory rerankers."""
14
+
15
+ @abstractmethod
16
+ def rerank(
17
+ self,
18
+ query: str,
19
+ graph_results: list,
20
+ top_k: int,
21
+ **kwargs,
22
+ ) -> list[tuple[TextualMemoryItem, float]]:
23
+ """Return top_k (item, score) sorted by score desc."""
24
+ raise NotImplementedError
@@ -0,0 +1,95 @@
1
+ # memos/reranker/cosine_local.py
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .base import BaseReranker
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from memos.memories.textual.item import TextualMemoryItem
11
+
12
+ try:
13
+ import numpy as _np
14
+
15
+ _HAS_NUMPY = True
16
+ except Exception:
17
+ _HAS_NUMPY = False
18
+
19
+
20
+ def _cosine_one_to_many(q: list[float], m: list[list[float]]) -> list[float]:
21
+ """
22
+ Compute cosine similarities between a single vector q and a matrix m (rows are candidates).
23
+ """
24
+ if not _HAS_NUMPY:
25
+
26
+ def dot(a, b): # lowercase per N806
27
+ return sum(x * y for x, y in zip(a, b, strict=False))
28
+
29
+ def norm(a): # lowercase per N806
30
+ return sum(x * x for x in a) ** 0.5
31
+
32
+ qn = norm(q) or 1e-10
33
+ sims = []
34
+ for v in m:
35
+ vn = norm(v) or 1e-10
36
+ sims.append(dot(q, v) / (qn * vn))
37
+ return sims
38
+
39
+ qv = _np.asarray(q, dtype=float) # lowercase
40
+ mv = _np.asarray(m, dtype=float) # lowercase
41
+ qn = _np.linalg.norm(qv) or 1e-10
42
+ mn = _np.linalg.norm(mv, axis=1) # lowercase
43
+ dots = mv @ qv
44
+ return (dots / (mn * qn + 1e-10)).tolist()
45
+
46
+
47
+ class CosineLocalReranker(BaseReranker):
48
+ def __init__(
49
+ self,
50
+ level_weights: dict[str, float] | None = None,
51
+ level_field: str = "background",
52
+ ):
53
+ self.level_weights = level_weights or {"topic": 1.0, "concept": 1.0, "fact": 1.0}
54
+ self.level_field = level_field
55
+
56
+ def rerank(
57
+ self,
58
+ query: str,
59
+ graph_results: list,
60
+ top_k: int,
61
+ **kwargs,
62
+ ) -> list[tuple[TextualMemoryItem, float]]:
63
+ if not graph_results:
64
+ return []
65
+
66
+ query_embedding: list[float] | None = kwargs.get("query_embedding")
67
+ if not query_embedding:
68
+ return [(item, 0.0) for item in graph_results[:top_k]]
69
+
70
+ items_with_emb = [
71
+ it
72
+ for it in graph_results
73
+ if getattr(it, "metadata", None) and getattr(it.metadata, "embedding", None)
74
+ ]
75
+ if not items_with_emb:
76
+ return [(item, 0.5) for item in graph_results[:top_k]]
77
+
78
+ cand_vecs = [it.metadata.embedding for it in items_with_emb]
79
+ sims = _cosine_one_to_many(query_embedding, cand_vecs)
80
+
81
+ def get_weight(it: TextualMemoryItem) -> float:
82
+ level = getattr(it.metadata, self.level_field, None)
83
+ return self.level_weights.get(level, 1.0)
84
+
85
+ weighted = [sim * get_weight(it) for sim, it in zip(sims, items_with_emb, strict=False)]
86
+ scored_pairs = list(zip(items_with_emb, weighted, strict=False))
87
+ scored_pairs.sort(key=lambda x: x[1], reverse=True)
88
+
89
+ top_items = scored_pairs[:top_k]
90
+ if len(top_items) < top_k:
91
+ chosen = {it.id for it, _ in top_items}
92
+ remain = [(it, -1.0) for it in graph_results if it.id not in chosen]
93
+ top_items.extend(remain[: top_k - len(top_items)])
94
+
95
+ return top_items
@@ -0,0 +1,43 @@
1
+ # memos/reranker/factory.py
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from .cosine_local import CosineLocalReranker
7
+ from .http_bge import HTTPBGEReranker
8
+ from .noop import NoopReranker
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from memos.configs.reranker import RerankerConfigFactory
13
+
14
+ from .base import BaseReranker
15
+
16
+
17
+ class RerankerFactory:
18
+ @staticmethod
19
+ def from_config(cfg: RerankerConfigFactory | None) -> BaseReranker | None:
20
+ if not cfg:
21
+ return None
22
+
23
+ backend = (cfg.backend or "").lower()
24
+ c: dict[str, Any] = cfg.config or {}
25
+
26
+ if backend in {"http_bge", "bge"}:
27
+ return HTTPBGEReranker(
28
+ reranker_url=c.get("url") or c.get("endpoint") or c.get("reranker_url"),
29
+ model=c.get("model", "bge-reranker-v2-m3"),
30
+ timeout=int(c.get("timeout", 10)),
31
+ headers_extra=c.get("headers_extra"),
32
+ )
33
+
34
+ if backend in {"cosine_local", "cosine"}:
35
+ return CosineLocalReranker(
36
+ level_weights=c.get("level_weights"),
37
+ level_field=c.get("level_field", "background"),
38
+ )
39
+
40
+ if backend in {"noop", "none", "disabled"}:
41
+ return NoopReranker()
42
+
43
+ raise ValueError(f"Unknown reranker backend: {cfg.backend}")
@@ -0,0 +1,99 @@
1
+ # memos/reranker/http_bge.py
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ import requests
9
+
10
+ from .base import BaseReranker
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from memos.memories.textual.item import TextualMemoryItem
15
+
16
+ _TAG1 = re.compile(r"^\s*\[[^\]]*\]\s*")
17
+
18
+
19
+ class HTTPBGEReranker(BaseReranker):
20
+ """
21
+ HTTP-based BGE reranker. Mirrors your old MemoryReranker, but configurable.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ reranker_url: str,
27
+ token: str = "",
28
+ model: str = "bge-reranker-v2-m3",
29
+ timeout: int = 10,
30
+ headers_extra: dict | None = None,
31
+ ):
32
+ if not reranker_url:
33
+ raise ValueError("reranker_url must not be empty")
34
+ self.reranker_url = reranker_url
35
+ self.token = token or ""
36
+ self.model = model
37
+ self.timeout = timeout
38
+ self.headers_extra = headers_extra or {}
39
+
40
+ def rerank(
41
+ self,
42
+ query: str,
43
+ graph_results: list,
44
+ top_k: int,
45
+ **kwargs,
46
+ ) -> list[tuple[TextualMemoryItem, float]]:
47
+ if not graph_results:
48
+ return []
49
+
50
+ documents = [
51
+ (_TAG1.sub("", m) if isinstance((m := getattr(item, "memory", None)), str) else m)
52
+ for item in graph_results
53
+ ]
54
+ documents = [d for d in documents if isinstance(d, str) and d]
55
+ if not documents:
56
+ return []
57
+
58
+ headers = {"Content-Type": "application/json", **self.headers_extra}
59
+ payload = {"model": self.model, "query": query, "documents": documents}
60
+
61
+ try:
62
+ resp = requests.post(
63
+ self.reranker_url, headers=headers, json=payload, timeout=self.timeout
64
+ )
65
+ resp.raise_for_status()
66
+ data = resp.json()
67
+
68
+ scored_items: list[tuple[TextualMemoryItem, float]] = []
69
+
70
+ if "results" in data:
71
+ rows = data.get("results", [])
72
+ for r in rows:
73
+ idx = r.get("index")
74
+ if isinstance(idx, int) and 0 <= idx < len(graph_results):
75
+ score = float(r.get("relevance_score", r.get("score", 0.0)))
76
+ scored_items.append((graph_results[idx], score))
77
+
78
+ scored_items.sort(key=lambda x: x[1], reverse=True)
79
+ return scored_items[: min(top_k, len(scored_items))]
80
+
81
+ elif "data" in data:
82
+ rows = data.get("data", [])
83
+ score_list = [float(r.get("score", 0.0)) for r in rows]
84
+
85
+ if len(score_list) < len(graph_results):
86
+ score_list += [0.0] * (len(graph_results) - len(score_list))
87
+ elif len(score_list) > len(graph_results):
88
+ score_list = score_list[: len(graph_results)]
89
+
90
+ scored_items = list(zip(graph_results, score_list, strict=False))
91
+ scored_items.sort(key=lambda x: x[1], reverse=True)
92
+ return scored_items[: min(top_k, len(scored_items))]
93
+
94
+ else:
95
+ return [(item, 0.0) for item in graph_results[:top_k]]
96
+
97
+ except Exception as e:
98
+ print(f"[HTTPBGEReranker] request failed: {e}")
99
+ return [(item, 0.0) for item in graph_results[:top_k]]
memos/reranker/noop.py ADDED
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .base import BaseReranker
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from memos.memories.textual.item import TextualMemoryItem
10
+
11
+
12
+ class NoopReranker(BaseReranker):
13
+ def rerank(
14
+ self, query: str, graph_results: list, top_k: int, **kwargs
15
+ ) -> list[tuple[TextualMemoryItem, float]]:
16
+ return [(item, 0.0) for item in graph_results[:top_k]]