hermes-plugin-qdrant-memory 0.2.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.
- hermes_plugin_qdrant_memory-0.2.0.dist-info/METADATA +495 -0
- hermes_plugin_qdrant_memory-0.2.0.dist-info/RECORD +29 -0
- hermes_plugin_qdrant_memory-0.2.0.dist-info/WHEEL +5 -0
- hermes_plugin_qdrant_memory-0.2.0.dist-info/entry_points.txt +5 -0
- hermes_plugin_qdrant_memory-0.2.0.dist-info/licenses/LICENSE +21 -0
- hermes_plugin_qdrant_memory-0.2.0.dist-info/top_level.txt +1 -0
- qdrant_memory/__init__.py +33 -0
- qdrant_memory/cli.py +887 -0
- qdrant_memory/config.py +313 -0
- qdrant_memory/config_schema.py +111 -0
- qdrant_memory/dedupe.py +26 -0
- qdrant_memory/embedding.py +114 -0
- qdrant_memory/extraction.py +90 -0
- qdrant_memory/inventory.py +153 -0
- qdrant_memory/ledger.py +686 -0
- qdrant_memory/mcp_server.py +57 -0
- qdrant_memory/migration.py +525 -0
- qdrant_memory/models.py +130 -0
- qdrant_memory/ownership.py +48 -0
- qdrant_memory/plugin.yaml +13 -0
- qdrant_memory/progress.py +89 -0
- qdrant_memory/provider.py +568 -0
- qdrant_memory/qdrant_store.py +332 -0
- qdrant_memory/reset.py +317 -0
- qdrant_memory/retry.py +51 -0
- qdrant_memory/runtime.py +314 -0
- qdrant_memory/standalone.py +203 -0
- qdrant_memory/tools.py +81 -0
- qdrant_memory/version.py +3 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Read-only memory inventory and portable export documents."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .models import now
|
|
10
|
+
|
|
11
|
+
EXPORT_FORMAT = "qdrant-memory-export"
|
|
12
|
+
EXPORT_VERSION = 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExportTargetExistsError(ValueError):
|
|
16
|
+
"""Refuse to overwrite an existing export without explicit authorization."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExportPublicationUnsupportedError(ValueError):
|
|
20
|
+
"""Refuse a no-clobber export the target filesystem cannot publish atomically."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def memory_view(point):
|
|
24
|
+
"""Return one stored memory as a scope-annotated inventory record.
|
|
25
|
+
|
|
26
|
+
The reserved schema identity point never reaches this view because the
|
|
27
|
+
store's scroll excludes it from memory records.
|
|
28
|
+
"""
|
|
29
|
+
value = point.payload or {}
|
|
30
|
+
return {
|
|
31
|
+
"point_id": str(point.id),
|
|
32
|
+
"user_id": value.get("user_id"),
|
|
33
|
+
"agent_id": value.get("agent_id"),
|
|
34
|
+
"text": value.get("text"),
|
|
35
|
+
"source": value.get("source"),
|
|
36
|
+
"session_id": value.get("session_id"),
|
|
37
|
+
"category": value.get("category") or [],
|
|
38
|
+
"importance": value.get("importance"),
|
|
39
|
+
"created_at": value.get("created_at"),
|
|
40
|
+
"updated_at": value.get("updated_at"),
|
|
41
|
+
"content_hash": value.get("content_hash"),
|
|
42
|
+
"cloud_origin": value.get("cloud_origin"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def export_record(point):
|
|
47
|
+
"""Return one stored memory in the shape the Mem0 JSON importer accepts.
|
|
48
|
+
|
|
49
|
+
Round trips preserve text, scope, categories and timestamps. Memories
|
|
50
|
+
migrated from Mem0 export their original Mem0 ID and re-import to the same
|
|
51
|
+
point; native memories export their point UUID for provenance, and
|
|
52
|
+
re-imports re-embed and derive fresh point IDs. Vectors are never exported.
|
|
53
|
+
"""
|
|
54
|
+
value = point.payload or {}
|
|
55
|
+
origin = value.get("cloud_origin")
|
|
56
|
+
origin = origin if isinstance(origin, dict) else {}
|
|
57
|
+
record = {
|
|
58
|
+
"id": origin.get("id") or str(point.id),
|
|
59
|
+
"memory": value.get("text", ""),
|
|
60
|
+
"user_id": value.get("user_id"),
|
|
61
|
+
"created_at": value.get("created_at"),
|
|
62
|
+
"updated_at": value.get("updated_at"),
|
|
63
|
+
"categories": value.get("category") or [],
|
|
64
|
+
"hash": value.get("content_hash"),
|
|
65
|
+
}
|
|
66
|
+
# Always export the scope's agent explicitly: an omitted field would let an
|
|
67
|
+
# importer substitute its default agent (the profile scope) and silently
|
|
68
|
+
# move no-agent memories into that agent's scope.
|
|
69
|
+
record["agent_id"] = value.get("agent_id")
|
|
70
|
+
metadata = value.get("metadata")
|
|
71
|
+
if isinstance(metadata, dict) and origin.get("provider") == "mem0":
|
|
72
|
+
# Round-trip fidelity: a Mem0-origin payload stores its source metadata
|
|
73
|
+
# under "legacy_mem0"; export the original object so re-import does not
|
|
74
|
+
# nest the wrapper inside another wrapper.
|
|
75
|
+
metadata = metadata.get("legacy_mem0", metadata)
|
|
76
|
+
if metadata:
|
|
77
|
+
record["metadata"] = metadata
|
|
78
|
+
return record
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def write_export(path, collection, points, force=False):
|
|
82
|
+
"""Stream stored memories to a portable export atomically, owner-only.
|
|
83
|
+
|
|
84
|
+
Records are written incrementally so large scopes never materialize a full
|
|
85
|
+
document in memory. An existing target is refused unless overwrite is
|
|
86
|
+
explicitly authorized; the refusal is atomic (hard-link publication).
|
|
87
|
+
Filesystems without hard links cannot refuse an overwrite without
|
|
88
|
+
publishing a partial target first, so non-force exports are refused there
|
|
89
|
+
and ``--force`` must authorize the write instead. Returns the number of
|
|
90
|
+
exported records.
|
|
91
|
+
"""
|
|
92
|
+
path = Path(path)
|
|
93
|
+
if path.exists() and not force:
|
|
94
|
+
raise ExportTargetExistsError("Export target already exists; pass --force to overwrite it")
|
|
95
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
96
|
+
dir=path.parent, prefix=path.name + ".", suffix=".tmp"
|
|
97
|
+
)
|
|
98
|
+
temporary = Path(temporary_name)
|
|
99
|
+
count = 0
|
|
100
|
+
try:
|
|
101
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
102
|
+
handle.write(
|
|
103
|
+
"{\n"
|
|
104
|
+
' "provider": "qdrant-memory",\n'
|
|
105
|
+
f' "format": {json.dumps(EXPORT_FORMAT)},\n'
|
|
106
|
+
f' "version": {EXPORT_VERSION},\n'
|
|
107
|
+
f' "exported_at": {json.dumps(now())},\n'
|
|
108
|
+
f' "collection": {json.dumps(collection)},\n'
|
|
109
|
+
' "memories": ['
|
|
110
|
+
)
|
|
111
|
+
first = True
|
|
112
|
+
for point in points:
|
|
113
|
+
record = export_record(point)
|
|
114
|
+
if not first:
|
|
115
|
+
handle.write(",")
|
|
116
|
+
handle.write("\n " + json.dumps(record, ensure_ascii=False))
|
|
117
|
+
first = False
|
|
118
|
+
count += 1
|
|
119
|
+
handle.write('\n ],\n "count": ' + str(count) + "\n}\n")
|
|
120
|
+
except BaseException:
|
|
121
|
+
temporary.unlink(missing_ok=True)
|
|
122
|
+
raise
|
|
123
|
+
if force:
|
|
124
|
+
os.replace(temporary, path)
|
|
125
|
+
else:
|
|
126
|
+
try:
|
|
127
|
+
_publish_no_clobber(temporary, path)
|
|
128
|
+
finally:
|
|
129
|
+
# Best-effort cleanup: the published target is already correct.
|
|
130
|
+
with contextlib.suppress(OSError):
|
|
131
|
+
temporary.unlink(missing_ok=True)
|
|
132
|
+
path.chmod(0o600)
|
|
133
|
+
return count
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _publish_no_clobber(temporary, path):
|
|
137
|
+
"""Publish a written temporary file without ever overwriting a target.
|
|
138
|
+
|
|
139
|
+
Hard-link publication is atomic and is the only supported no-clobber
|
|
140
|
+
mechanism: a filesystem that cannot hard-link also cannot refuse an
|
|
141
|
+
overwrite without publishing a partial target first, so the export is
|
|
142
|
+
refused there and the caller must authorize ``--force`` instead.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
os.link(temporary, path)
|
|
146
|
+
except FileExistsError:
|
|
147
|
+
raise ExportTargetExistsError(
|
|
148
|
+
"Export target already exists; pass --force to overwrite it"
|
|
149
|
+
) from None
|
|
150
|
+
except OSError as error:
|
|
151
|
+
raise ExportPublicationUnsupportedError(
|
|
152
|
+
"target filesystem does not support atomic no-clobber publication"
|
|
153
|
+
) from error
|