atomicmemory 1.0.0__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.
Files changed (67) hide show
  1. atomicmemory/__init__.py +166 -0
  2. atomicmemory/_version.py +3 -0
  3. atomicmemory/client/__init__.py +22 -0
  4. atomicmemory/client/async_memory_client.py +202 -0
  5. atomicmemory/client/atomic_memory_client.py +181 -0
  6. atomicmemory/client/memory_client.py +292 -0
  7. atomicmemory/core/__init__.py +34 -0
  8. atomicmemory/core/errors.py +122 -0
  9. atomicmemory/core/events.py +65 -0
  10. atomicmemory/core/logging.py +37 -0
  11. atomicmemory/core/retry.py +124 -0
  12. atomicmemory/core/validation.py +22 -0
  13. atomicmemory/embeddings/__init__.py +16 -0
  14. atomicmemory/embeddings/base.py +39 -0
  15. atomicmemory/embeddings/sentence_transformers.py +104 -0
  16. atomicmemory/kv_cache/__init__.py +17 -0
  17. atomicmemory/kv_cache/adapter.py +50 -0
  18. atomicmemory/kv_cache/memory_storage.py +98 -0
  19. atomicmemory/kv_cache/sqlite_storage.py +122 -0
  20. atomicmemory/memory/__init__.py +82 -0
  21. atomicmemory/memory/filters.py +68 -0
  22. atomicmemory/memory/pipeline.py +42 -0
  23. atomicmemory/memory/provider.py +397 -0
  24. atomicmemory/memory/registry.py +95 -0
  25. atomicmemory/memory/service.py +199 -0
  26. atomicmemory/memory/types.py +398 -0
  27. atomicmemory/providers/__init__.py +5 -0
  28. atomicmemory/providers/atomicmemory/__init__.py +43 -0
  29. atomicmemory/providers/atomicmemory/agents.py +156 -0
  30. atomicmemory/providers/atomicmemory/async_handle_impl.py +198 -0
  31. atomicmemory/providers/atomicmemory/async_provider.py +245 -0
  32. atomicmemory/providers/atomicmemory/audit.py +74 -0
  33. atomicmemory/providers/atomicmemory/config.py +38 -0
  34. atomicmemory/providers/atomicmemory/config_handle.py +123 -0
  35. atomicmemory/providers/atomicmemory/handle.py +513 -0
  36. atomicmemory/providers/atomicmemory/handle_impl.py +325 -0
  37. atomicmemory/providers/atomicmemory/http.py +255 -0
  38. atomicmemory/providers/atomicmemory/lessons.py +133 -0
  39. atomicmemory/providers/atomicmemory/lifecycle.py +202 -0
  40. atomicmemory/providers/atomicmemory/mappers.py +125 -0
  41. atomicmemory/providers/atomicmemory/path.py +20 -0
  42. atomicmemory/providers/atomicmemory/provider.py +300 -0
  43. atomicmemory/providers/atomicmemory/scope_mapper.py +98 -0
  44. atomicmemory/providers/mem0/__init__.py +41 -0
  45. atomicmemory/providers/mem0/async_provider.py +191 -0
  46. atomicmemory/providers/mem0/config.py +51 -0
  47. atomicmemory/providers/mem0/http.py +195 -0
  48. atomicmemory/providers/mem0/mappers.py +145 -0
  49. atomicmemory/providers/mem0/provider.py +202 -0
  50. atomicmemory/py.typed +0 -0
  51. atomicmemory/search/__init__.py +47 -0
  52. atomicmemory/search/chunking.py +161 -0
  53. atomicmemory/search/ranking.py +94 -0
  54. atomicmemory/search/semantic_search.py +130 -0
  55. atomicmemory/search/similarity.py +110 -0
  56. atomicmemory/storage/__init__.py +63 -0
  57. atomicmemory/storage/_mapping.py +305 -0
  58. atomicmemory/storage/async_client.py +208 -0
  59. atomicmemory/storage/client.py +339 -0
  60. atomicmemory/storage/errors.py +115 -0
  61. atomicmemory/storage/types.py +305 -0
  62. atomicmemory/utils/__init__.py +5 -0
  63. atomicmemory/utils/environment.py +23 -0
  64. atomicmemory-1.0.0.dist-info/METADATA +146 -0
  65. atomicmemory-1.0.0.dist-info/RECORD +67 -0
  66. atomicmemory-1.0.0.dist-info/WHEEL +4 -0
  67. atomicmemory-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,195 @@
1
+ """HTTP transport for the Mem0 provider — provider-tagged sync + async helpers.
2
+
3
+ Port of the Mem0-bound layer in `atomicmemory-sdk/src/memory/mem0-provider/http.ts`
4
+ (which itself wraps the shared `shared/http-client.ts`). Same error-mapping
5
+ policy as the AtomicMemory transport, but errors are tagged with
6
+ ``provider="mem0"`` so callers can route them.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ import httpx
15
+
16
+ from atomicmemory.core.errors import NetworkError, ProviderError, RateLimitError
17
+
18
+ _PROVIDER_NAME = "mem0"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class HttpOptions:
23
+ api_url: str
24
+ api_key: str | None
25
+ timeout_seconds: float
26
+
27
+
28
+ def _headers(options: HttpOptions, extra: dict[str, str] | None = None) -> dict[str, str]:
29
+ headers: dict[str, str] = {"Content-Type": "application/json"}
30
+ if options.api_key:
31
+ headers["Authorization"] = f"Bearer {options.api_key}"
32
+ if extra:
33
+ headers.update(extra)
34
+ return headers
35
+
36
+
37
+ def _parse_retry_after(value: str | None) -> float | None:
38
+ if not value:
39
+ return None
40
+ try:
41
+ return float(value)
42
+ except ValueError:
43
+ return None
44
+
45
+
46
+ def _raise_for_status(response: httpx.Response, path: str) -> None:
47
+ if response.status_code == 429:
48
+ raise RateLimitError(
49
+ "Rate limited",
50
+ provider=_PROVIDER_NAME,
51
+ retry_after_seconds=_parse_retry_after(response.headers.get("Retry-After")),
52
+ context={"path": path},
53
+ )
54
+ if response.is_success:
55
+ return
56
+ body_text = response.text
57
+ body_decoded: Any = body_text
58
+ try:
59
+ body_decoded = response.json()
60
+ except (ValueError, httpx.DecodingError):
61
+ body_decoded = body_text
62
+ raise ProviderError(
63
+ f"HTTP {response.status_code}: {body_text or response.reason_phrase}",
64
+ provider=_PROVIDER_NAME,
65
+ status_code=response.status_code,
66
+ response_body=body_decoded,
67
+ context={"path": path},
68
+ )
69
+
70
+
71
+ def _request(
72
+ client: httpx.Client,
73
+ options: HttpOptions,
74
+ method: str,
75
+ path: str,
76
+ *,
77
+ json: Any | None = None,
78
+ ) -> httpx.Response:
79
+ url = f"{options.api_url}{path}"
80
+ try:
81
+ return client.request(method, url, headers=_headers(options), json=json, timeout=options.timeout_seconds)
82
+ except httpx.TimeoutException as exc:
83
+ raise NetworkError(
84
+ f"Timeout after {options.timeout_seconds}s",
85
+ provider=_PROVIDER_NAME,
86
+ cause=exc,
87
+ context={"path": path, "method": method},
88
+ ) from exc
89
+ except httpx.RequestError as exc:
90
+ raise NetworkError(
91
+ f"Transport error: {exc}",
92
+ provider=_PROVIDER_NAME,
93
+ cause=exc,
94
+ context={"path": path, "method": method},
95
+ ) from exc
96
+
97
+
98
+ def fetch_json(
99
+ client: httpx.Client,
100
+ options: HttpOptions,
101
+ path: str,
102
+ *,
103
+ method: str = "GET",
104
+ json: Any | None = None,
105
+ ) -> Any:
106
+ response = _request(client, options, method, path, json=json)
107
+ _raise_for_status(response, path)
108
+ return response.json()
109
+
110
+
111
+ def fetch_json_or_none(
112
+ client: httpx.Client,
113
+ options: HttpOptions,
114
+ path: str,
115
+ *,
116
+ method: str = "GET",
117
+ json: Any | None = None,
118
+ ) -> Any | None:
119
+ response = _request(client, options, method, path, json=json)
120
+ if response.status_code == 404:
121
+ return None
122
+ _raise_for_status(response, path)
123
+ return response.json()
124
+
125
+
126
+ def delete_ignore_404(client: httpx.Client, options: HttpOptions, path: str) -> None:
127
+ response = _request(client, options, "DELETE", path)
128
+ if response.status_code == 404:
129
+ return
130
+ _raise_for_status(response, path)
131
+
132
+
133
+ # ---- async ---------------------------------------------------------------
134
+
135
+
136
+ async def _arequest(
137
+ client: httpx.AsyncClient,
138
+ options: HttpOptions,
139
+ method: str,
140
+ path: str,
141
+ *,
142
+ json: Any | None = None,
143
+ ) -> httpx.Response:
144
+ url = f"{options.api_url}{path}"
145
+ try:
146
+ return await client.request(method, url, headers=_headers(options), json=json, timeout=options.timeout_seconds)
147
+ except httpx.TimeoutException as exc:
148
+ raise NetworkError(
149
+ f"Timeout after {options.timeout_seconds}s",
150
+ provider=_PROVIDER_NAME,
151
+ cause=exc,
152
+ context={"path": path, "method": method},
153
+ ) from exc
154
+ except httpx.RequestError as exc:
155
+ raise NetworkError(
156
+ f"Transport error: {exc}",
157
+ provider=_PROVIDER_NAME,
158
+ cause=exc,
159
+ context={"path": path, "method": method},
160
+ ) from exc
161
+
162
+
163
+ async def afetch_json(
164
+ client: httpx.AsyncClient,
165
+ options: HttpOptions,
166
+ path: str,
167
+ *,
168
+ method: str = "GET",
169
+ json: Any | None = None,
170
+ ) -> Any:
171
+ response = await _arequest(client, options, method, path, json=json)
172
+ _raise_for_status(response, path)
173
+ return response.json()
174
+
175
+
176
+ async def afetch_json_or_none(
177
+ client: httpx.AsyncClient,
178
+ options: HttpOptions,
179
+ path: str,
180
+ *,
181
+ method: str = "GET",
182
+ json: Any | None = None,
183
+ ) -> Any | None:
184
+ response = await _arequest(client, options, method, path, json=json)
185
+ if response.status_code == 404:
186
+ return None
187
+ _raise_for_status(response, path)
188
+ return response.json()
189
+
190
+
191
+ async def adelete_ignore_404(client: httpx.AsyncClient, options: HttpOptions, path: str) -> None:
192
+ response = await _arequest(client, options, "DELETE", path)
193
+ if response.status_code == 404:
194
+ return
195
+ _raise_for_status(response, path)
@@ -0,0 +1,145 @@
1
+ """Wire-format mappers + body builders for the Mem0 provider.
2
+
3
+ Port of `atomicmemory-sdk/src/memory/mem0-provider/mappers.ts`. Pure
4
+ functions — shared by the sync and async providers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from typing import Any
11
+
12
+ from atomicmemory.memory.types import (
13
+ IngestInput,
14
+ IngestResult,
15
+ Memory,
16
+ Scope,
17
+ SearchResult,
18
+ )
19
+ from atomicmemory.providers.mem0.config import Mem0ProviderConfig
20
+
21
+ _MetadataDict = dict[str, Any]
22
+
23
+
24
+ def unwrap_mem0_array(raw: Any) -> list[_MetadataDict]:
25
+ """Return a list of memory dicts from either a bare array or ``{results: [...]}``."""
26
+ if isinstance(raw, list):
27
+ return list(raw)
28
+ if isinstance(raw, dict) and "results" in raw:
29
+ results = raw["results"]
30
+ if isinstance(results, list):
31
+ return list(results)
32
+ return []
33
+
34
+
35
+ def _parse_iso(value: str | None) -> datetime | None:
36
+ if value is None:
37
+ return None
38
+ text = value.replace("Z", "+00:00") if value.endswith("Z") else value
39
+ return datetime.fromisoformat(text)
40
+
41
+
42
+ def _extract_memory_text(raw: dict[str, Any]) -> str:
43
+ """Pick the memory text from either flat ``memory`` or nested ``data.memory``."""
44
+ if raw.get("memory") is not None:
45
+ return str(raw["memory"])
46
+ data = raw.get("data")
47
+ if isinstance(data, dict) and data.get("memory") is not None:
48
+ return str(data["memory"])
49
+ return ""
50
+
51
+
52
+ def to_memory(raw: dict[str, Any], scope: Scope) -> Memory:
53
+ return Memory(
54
+ id=str(raw["id"]),
55
+ content=_extract_memory_text(raw),
56
+ scope=scope,
57
+ created_at=_parse_iso(raw.get("created_at")) or datetime.now().astimezone(),
58
+ updated_at=_parse_iso(raw.get("updated_at")),
59
+ metadata=raw.get("metadata"),
60
+ )
61
+
62
+
63
+ def to_search_result(raw: dict[str, Any], scope: Scope) -> SearchResult:
64
+ raw_score = raw.get("score")
65
+ score: float = float(raw_score) if raw_score is not None else 0.0
66
+ return SearchResult(memory=to_memory(raw, scope), score=score)
67
+
68
+
69
+ def to_ingest_result(raw_memories: list[dict[str, Any]]) -> IngestResult:
70
+ created: list[str] = []
71
+ updated: list[str] = []
72
+ unchanged: list[str] = []
73
+ for mem in raw_memories:
74
+ event = (mem.get("event") or "ADD").upper()
75
+ if event == "ADD":
76
+ created.append(str(mem.get("id", "")))
77
+ elif event == "UPDATE":
78
+ updated.append(str(mem.get("id", "")))
79
+ elif event in {"NONE", "NOOP"}:
80
+ unchanged.append(str(mem.get("id", "")))
81
+ else:
82
+ created.append(str(mem.get("id", "")))
83
+ return IngestResult(created=created, updated=updated, unchanged=unchanged)
84
+
85
+
86
+ def resolve_infer_flag(input: IngestInput, config: Mem0ProviderConfig) -> bool:
87
+ metadata = input.metadata or {}
88
+ metadata_infer = metadata.get("infer") if isinstance(metadata, dict) else None
89
+ if isinstance(metadata_infer, bool):
90
+ return metadata_infer
91
+ return config.default_infer
92
+
93
+
94
+ def build_ingest_body(input: IngestInput, user_id: str, config: Mem0ProviderConfig) -> dict[str, Any]:
95
+ """Compose Mem0's ``POST /v1/memories/`` request body."""
96
+ metadata = input.metadata or {}
97
+ clean_metadata = {k: v for k, v in metadata.items() if k != "infer"} if isinstance(metadata, dict) else {}
98
+ body: dict[str, Any] = {
99
+ "user_id": user_id,
100
+ "infer": resolve_infer_flag(input, config),
101
+ }
102
+ if clean_metadata:
103
+ body["metadata"] = clean_metadata
104
+ if input.mode == "text":
105
+ body["messages"] = [{"role": "user", "content": input.content}]
106
+ elif input.mode == "messages":
107
+ body["messages"] = [{"role": m.role, "content": m.content} for m in input.messages]
108
+ _apply_enterprise_fields(body, config)
109
+ _apply_scope_identifiers(body, input.scope)
110
+ return body
111
+
112
+
113
+ def build_search_body(
114
+ query: str,
115
+ scope: Scope,
116
+ config: Mem0ProviderConfig,
117
+ limit: int | None = None,
118
+ ) -> dict[str, Any]:
119
+ """Compose Mem0 v2's ``POST /v2/memories/search/`` body with nested filters."""
120
+ filters: dict[str, Any] = {}
121
+ if scope.user:
122
+ filters["user_id"] = scope.user
123
+ if scope.agent:
124
+ filters["agent_id"] = scope.agent
125
+ if scope.thread:
126
+ filters["run_id"] = scope.thread
127
+ body: dict[str, Any] = {"query": query, "filters": filters}
128
+ if limit is not None:
129
+ body["limit"] = limit
130
+ _apply_enterprise_fields(body, config)
131
+ return body
132
+
133
+
134
+ def _apply_enterprise_fields(body: dict[str, Any], config: Mem0ProviderConfig) -> None:
135
+ if config.org_id:
136
+ body["org_id"] = config.org_id
137
+ if config.project_id:
138
+ body["project_id"] = config.project_id
139
+
140
+
141
+ def _apply_scope_identifiers(body: dict[str, Any], scope: Scope) -> None:
142
+ if scope.agent:
143
+ body["agent_id"] = scope.agent
144
+ if scope.thread:
145
+ body["run_id"] = scope.thread
@@ -0,0 +1,202 @@
1
+ """Sync Mem0Provider — V3 core + Health.
2
+
3
+ Port of `atomicmemory-sdk/src/memory/mem0-provider/mem0-provider.ts`.
4
+ Mem0's ``/memories`` endpoint always runs server-side extraction, so
5
+ ``verbatim`` ingest is rejected — capabilities advertise only
6
+ ``text`` + ``messages`` modes, and ``do_ingest`` raises
7
+ ``ProviderError("Unsupported")`` if a verbatim input slips through.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import time
14
+ from typing import Any
15
+
16
+ import httpx
17
+
18
+ from atomicmemory.core.errors import ProviderError
19
+ from atomicmemory.memory.provider import BaseMemoryProvider
20
+ from atomicmemory.memory.types import (
21
+ Capabilities,
22
+ CapabilitiesExtensions,
23
+ CapabilitiesRequiredScope,
24
+ HealthStatus,
25
+ IngestInput,
26
+ IngestResult,
27
+ ListRequest,
28
+ ListResultPage,
29
+ Memory,
30
+ MemoryRef,
31
+ SearchRequest,
32
+ SearchResultPage,
33
+ )
34
+ from atomicmemory.providers.mem0.config import Mem0ProviderConfig
35
+ from atomicmemory.providers.mem0.http import (
36
+ HttpOptions,
37
+ delete_ignore_404,
38
+ fetch_json,
39
+ fetch_json_or_none,
40
+ )
41
+ from atomicmemory.providers.mem0.mappers import (
42
+ build_ingest_body,
43
+ build_search_body,
44
+ resolve_infer_flag,
45
+ to_ingest_result,
46
+ to_memory,
47
+ to_search_result,
48
+ unwrap_mem0_array,
49
+ )
50
+
51
+ _logger = logging.getLogger("atomicmemory.providers.mem0")
52
+
53
+
54
+ class Mem0Provider(BaseMemoryProvider):
55
+ """Sync HTTP-backed V3 provider for Mem0 (OSS + hosted)."""
56
+
57
+ name = "mem0"
58
+
59
+ def __init__(self, config: Mem0ProviderConfig) -> None:
60
+ self._config = config
61
+ self._http_options = HttpOptions(
62
+ api_url=config.api_url.rstrip("/"),
63
+ api_key=config.api_key,
64
+ timeout_seconds=config.timeout_seconds,
65
+ )
66
+ self._prefix = config.path_prefix
67
+ self._client: httpx.Client | None = None
68
+ self._initialized = False
69
+
70
+ def initialize(self) -> None:
71
+ if self._client is None:
72
+ self._client = httpx.Client()
73
+ self._initialized = True
74
+
75
+ def close(self) -> None:
76
+ if self._client is not None:
77
+ self._client.close()
78
+ self._client = None
79
+ self._initialized = False
80
+
81
+ # ------------------------------------------------------------------
82
+ # V3 core methods
83
+ # ------------------------------------------------------------------
84
+
85
+ def do_ingest(self, input: IngestInput) -> IngestResult:
86
+ if input.mode == "verbatim":
87
+ raise ProviderError(
88
+ "mem0 does not support verbatim ingest; use the atomicmemory provider for "
89
+ "deterministic one-input-equals-one-memory storage.",
90
+ provider=self.name,
91
+ context={"operation": "ingest", "mode": "verbatim"},
92
+ )
93
+ user_id = input.scope.user or ""
94
+ body = build_ingest_body(input, user_id, self._config)
95
+ should_defer = self._config.defer_inference and resolve_infer_flag(input, self._config)
96
+ if should_defer:
97
+ body["infer"] = False
98
+ raw = fetch_json(
99
+ self._require_client(),
100
+ self._http_options,
101
+ self._path("/memories/"),
102
+ method="POST",
103
+ json=body,
104
+ )
105
+ memories = unwrap_mem0_array(raw)
106
+ if should_defer:
107
+ self._fire_background_inference(body)
108
+ return to_ingest_result(memories)
109
+
110
+ def do_search(self, request: SearchRequest) -> SearchResultPage:
111
+ body = build_search_body(request.query, request.scope, self._config, request.limit)
112
+ raw = fetch_json(
113
+ self._require_client(),
114
+ self._http_options,
115
+ self._search_path(),
116
+ method="POST",
117
+ json=body,
118
+ )
119
+ results = [to_search_result(m, request.scope) for m in unwrap_mem0_array(raw)]
120
+ return SearchResultPage(results=results)
121
+
122
+ def do_get(self, ref: MemoryRef) -> Memory | None:
123
+ raw = fetch_json_or_none(self._require_client(), self._http_options, self._path(f"/memories/{ref.id}/"))
124
+ if raw is None:
125
+ return None
126
+ return to_memory(raw, ref.scope)
127
+
128
+ def do_delete(self, ref: MemoryRef) -> None:
129
+ delete_ignore_404(self._require_client(), self._http_options, self._path(f"/memories/{ref.id}/"))
130
+
131
+ def do_list(self, request: ListRequest) -> ListResultPage:
132
+ limit = request.limit if request.limit is not None else 20
133
+ offset = int(request.cursor) if request.cursor else 0
134
+ page = (offset // limit) + 1 if offset > 0 else None
135
+ path = self._path(f"/memories/?user_id={request.scope.user or ''}&page_size={limit}")
136
+ if page is not None:
137
+ path += f"&page={page}"
138
+ raw = fetch_json(self._require_client(), self._http_options, path)
139
+ memories = [to_memory(m, request.scope) for m in unwrap_mem0_array(raw)]
140
+ next_offset = offset + len(memories)
141
+ cursor = str(next_offset) if len(memories) == limit else None
142
+ return ListResultPage(memories=memories, cursor=cursor)
143
+
144
+ # ------------------------------------------------------------------
145
+ # Capabilities + extensions
146
+ # ------------------------------------------------------------------
147
+
148
+ def capabilities(self) -> Capabilities:
149
+ return Capabilities(
150
+ ingest_modes=["text", "messages"],
151
+ required_scope=CapabilitiesRequiredScope(default=["user"]),
152
+ extensions=CapabilitiesExtensions(health=True),
153
+ )
154
+
155
+ def get_extension(self, name: str) -> Any | None:
156
+ if name == "health":
157
+ return self
158
+ return None
159
+
160
+ def health(self) -> HealthStatus:
161
+ start = time.monotonic()
162
+ try:
163
+ fetch_json(
164
+ self._require_client(),
165
+ self._http_options,
166
+ self._path("/memories/?user_id=health-check&page_size=1"),
167
+ )
168
+ return HealthStatus(ok=True, latency_ms=(time.monotonic() - start) * 1000.0)
169
+ except (ProviderError, ValueError):
170
+ return HealthStatus(ok=False, latency_ms=(time.monotonic() - start) * 1000.0)
171
+
172
+ # ------------------------------------------------------------------
173
+ # Internals
174
+ # ------------------------------------------------------------------
175
+
176
+ def _path(self, endpoint: str) -> str:
177
+ return f"{self._prefix}{endpoint}"
178
+
179
+ def _search_path(self) -> str:
180
+ # mem0 2.0 split search out of the v1 family.
181
+ return "/memories/search/" if self._prefix == "" else "/v2/memories/search/"
182
+
183
+ def _require_client(self) -> httpx.Client:
184
+ if self._client is None:
185
+ raise ProviderError(
186
+ "Mem0Provider is not initialized. Call initialize() first.",
187
+ provider=self.name,
188
+ )
189
+ return self._client
190
+
191
+ def _fire_background_inference(self, body: dict[str, Any]) -> None:
192
+ """Best-effort re-ingest with infer=true. Errors are logged, never raised."""
193
+ try:
194
+ fetch_json(
195
+ self._require_client(),
196
+ self._http_options,
197
+ self._path("/memories/"),
198
+ method="POST",
199
+ json={**body, "infer": True},
200
+ )
201
+ except Exception as exc:
202
+ _logger.warning("[mem0] deferred AUDN failed: %s", exc)
atomicmemory/py.typed ADDED
File without changes
@@ -0,0 +1,47 @@
1
+ """Local search primitives — similarity, chunking, ranking, and orchestration.
2
+
3
+ Port of `atomicmemory-sdk/src/search/` and the chunking helpers at
4
+ `src/utils/chunking.ts`. These are pure Python (numpy + stdlib) and work
5
+ without any provider configured — useful for offline experiments,
6
+ fixture-driven benchmarks, and embedding the SDK in non-Atomicmem
7
+ contexts.
8
+ """
9
+
10
+ from atomicmemory.search.chunking import (
11
+ ChunkOptions,
12
+ ChunkResult,
13
+ chunk_by_paragraphs,
14
+ chunk_by_sentences,
15
+ chunk_text,
16
+ chunk_text_with_metadata,
17
+ )
18
+ from atomicmemory.search.ranking import RankingConfig, rerank
19
+ from atomicmemory.search.semantic_search import (
20
+ SemanticSearch,
21
+ SemanticSearchConfig,
22
+ SemanticSearchResult,
23
+ StoredContext,
24
+ )
25
+ from atomicmemory.search.similarity import (
26
+ cosine_similarity,
27
+ find_top_k,
28
+ rank_by_similarity,
29
+ )
30
+
31
+ __all__ = [
32
+ "ChunkOptions",
33
+ "ChunkResult",
34
+ "RankingConfig",
35
+ "SemanticSearch",
36
+ "SemanticSearchConfig",
37
+ "SemanticSearchResult",
38
+ "StoredContext",
39
+ "chunk_by_paragraphs",
40
+ "chunk_by_sentences",
41
+ "chunk_text",
42
+ "chunk_text_with_metadata",
43
+ "cosine_similarity",
44
+ "find_top_k",
45
+ "rank_by_similarity",
46
+ "rerank",
47
+ ]