zin 0.0.1__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.
- zin-0.0.1/.gitignore +5 -0
- zin-0.0.1/PKG-INFO +6 -0
- zin-0.0.1/pyproject.toml +15 -0
- zin-0.0.1/zin/__init__.py +4 -0
- zin-0.0.1/zin/client.py +157 -0
- zin-0.0.1/zin/models.py +20 -0
zin-0.0.1/PKG-INFO
ADDED
zin-0.0.1/pyproject.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zin"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Zin AI — long-term memory for AI agents"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.28",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[tool.hatch.build.targets.wheel]
|
|
15
|
+
packages = ["zin"]
|
zin-0.0.1/zin/client.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Zin Memory SDK — async client for the Zin memory server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from zin.models import (
|
|
8
|
+
ConversationMessage,
|
|
9
|
+
Memory,
|
|
10
|
+
SearchResult,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ZinMemory:
|
|
15
|
+
"""Async client for the Zin memory API.
|
|
16
|
+
|
|
17
|
+
Can be used as a singleton — user_id and namespaces are passed per-call,
|
|
18
|
+
not at init time.
|
|
19
|
+
|
|
20
|
+
Usage::
|
|
21
|
+
|
|
22
|
+
client = ZinMemory("http://localhost:8000")
|
|
23
|
+
|
|
24
|
+
# Add a memory
|
|
25
|
+
mem_id = await client.add("Likes dark mode", user_id="u1")
|
|
26
|
+
|
|
27
|
+
# Search
|
|
28
|
+
results = await client.search("preferences", user_id="u1")
|
|
29
|
+
|
|
30
|
+
# Process a conversation (fire-and-forget on server side)
|
|
31
|
+
await client.process_memory(
|
|
32
|
+
messages=[("user", "I love hiking"), ("assistant", "Nice!")],
|
|
33
|
+
user_id="u1",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Cleanup
|
|
37
|
+
await client.close()
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, base_url: str = "http://localhost:8000", timeout: float = 10.0):
|
|
41
|
+
self._http = httpx.AsyncClient(base_url=base_url, timeout=timeout)
|
|
42
|
+
|
|
43
|
+
async def close(self) -> None:
|
|
44
|
+
await self._http.aclose()
|
|
45
|
+
|
|
46
|
+
async def __aenter__(self) -> ZinMemory:
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
async def __aexit__(self, *exc) -> None:
|
|
50
|
+
await self.close()
|
|
51
|
+
|
|
52
|
+
# ── Add ───────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
async def add(
|
|
55
|
+
self,
|
|
56
|
+
text: str,
|
|
57
|
+
*,
|
|
58
|
+
user_id: str | None = None,
|
|
59
|
+
namespace: str = "personal",
|
|
60
|
+
metadata: dict | None = None,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""Add a memory. Returns the memory ID."""
|
|
63
|
+
payload: dict = {"text": text, "namespace": namespace, "metadata": metadata or {}}
|
|
64
|
+
if user_id:
|
|
65
|
+
payload["user_id"] = user_id
|
|
66
|
+
resp = await self._http.post("/add", json=payload)
|
|
67
|
+
resp.raise_for_status()
|
|
68
|
+
return resp.json()["id"]
|
|
69
|
+
|
|
70
|
+
# ── Search ────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
async def search(
|
|
73
|
+
self,
|
|
74
|
+
query: str,
|
|
75
|
+
*,
|
|
76
|
+
user_id: str | None = None,
|
|
77
|
+
namespace: str | None = None,
|
|
78
|
+
top_k: int = 5,
|
|
79
|
+
) -> list[SearchResult]:
|
|
80
|
+
"""Search for memories by semantic similarity."""
|
|
81
|
+
payload: dict = {"query": query, "top_k": top_k}
|
|
82
|
+
if user_id:
|
|
83
|
+
payload["user_id"] = user_id
|
|
84
|
+
if namespace:
|
|
85
|
+
payload["namespace"] = namespace
|
|
86
|
+
resp = await self._http.post("/search", json=payload)
|
|
87
|
+
resp.raise_for_status()
|
|
88
|
+
return [
|
|
89
|
+
SearchResult(
|
|
90
|
+
id=r["id"],
|
|
91
|
+
text=r["text"],
|
|
92
|
+
metadata=r["metadata"],
|
|
93
|
+
namespace=r["namespace"],
|
|
94
|
+
distance=r["distance"],
|
|
95
|
+
)
|
|
96
|
+
for r in resp.json()
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# ── Process memory ────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
async def process_memory(
|
|
102
|
+
self,
|
|
103
|
+
messages: list[tuple[str, str] | ConversationMessage],
|
|
104
|
+
*,
|
|
105
|
+
user_id: str | None = None,
|
|
106
|
+
namespaces: dict[str, str] | None = None,
|
|
107
|
+
store_personal: bool = True,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Send a conversation window to the memory agent for processing.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
messages: List of (role, content) tuples or ConversationMessage objects.
|
|
113
|
+
user_id: User ID for personal namespace scoping.
|
|
114
|
+
namespaces: Shared namespace dict (e.g. {"company": "Company knowledge"}).
|
|
115
|
+
store_personal: Whether the agent can store personal memories.
|
|
116
|
+
"""
|
|
117
|
+
msg_dicts = []
|
|
118
|
+
for m in messages:
|
|
119
|
+
if isinstance(m, ConversationMessage):
|
|
120
|
+
msg_dicts.append({"role": m.role, "content": m.content})
|
|
121
|
+
else:
|
|
122
|
+
msg_dicts.append({"role": m[0], "content": m[1]})
|
|
123
|
+
|
|
124
|
+
payload: dict = {"messages": msg_dicts, "store_personal": store_personal}
|
|
125
|
+
if user_id:
|
|
126
|
+
payload["user_id"] = user_id
|
|
127
|
+
if namespaces:
|
|
128
|
+
payload["namespaces"] = namespaces
|
|
129
|
+
resp = await self._http.post("/process-memory", json=payload)
|
|
130
|
+
resp.raise_for_status()
|
|
131
|
+
|
|
132
|
+
# ── Delete all memories ─────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
async def delete_all_memories(self) -> int:
|
|
135
|
+
"""Delete all memories. Returns the number deleted."""
|
|
136
|
+
resp = await self._http.delete("/memories")
|
|
137
|
+
resp.raise_for_status()
|
|
138
|
+
return resp.json()["deleted"]
|
|
139
|
+
|
|
140
|
+
# ── List memories ─────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
async def list_memories(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
namespace: str | None = None,
|
|
146
|
+
limit: int = 100,
|
|
147
|
+
) -> list[Memory]:
|
|
148
|
+
"""List stored memories (no vector search). For dev/debugging."""
|
|
149
|
+
params: dict = {"limit": limit}
|
|
150
|
+
if namespace:
|
|
151
|
+
params["namespace"] = namespace
|
|
152
|
+
resp = await self._http.get("/memories", params=params)
|
|
153
|
+
resp.raise_for_status()
|
|
154
|
+
return [
|
|
155
|
+
Memory(id=r["id"], text=r["text"], metadata=r["metadata"], namespace=r["namespace"])
|
|
156
|
+
for r in resp.json()
|
|
157
|
+
]
|
zin-0.0.1/zin/models.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Memory:
|
|
6
|
+
id: str
|
|
7
|
+
text: str
|
|
8
|
+
metadata: dict
|
|
9
|
+
namespace: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SearchResult(Memory):
|
|
14
|
+
distance: float = 0.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ConversationMessage:
|
|
19
|
+
role: str
|
|
20
|
+
content: str
|