kognite 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.
- kognite-0.1.0/PKG-INFO +101 -0
- kognite-0.1.0/README.md +79 -0
- kognite-0.1.0/kognite/__init__.py +22 -0
- kognite-0.1.0/kognite/client.py +282 -0
- kognite-0.1.0/kognite.egg-info/PKG-INFO +101 -0
- kognite-0.1.0/kognite.egg-info/SOURCES.txt +8 -0
- kognite-0.1.0/kognite.egg-info/dependency_links.txt +1 -0
- kognite-0.1.0/kognite.egg-info/top_level.txt +1 -0
- kognite-0.1.0/pyproject.toml +36 -0
- kognite-0.1.0/setup.cfg +4 -0
kognite-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kognite
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Kognite — long-term memory for AI agents (hybrid semantic search, memory formation, knowledge graph)
|
|
5
|
+
Author-email: Kognite <hello@kognite.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://kognite.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/global-software-development-eu/memcore-portable-agent-memory-saas
|
|
9
|
+
Keywords: kognite,agent-memory,memory,llm,rag,sdk,ai
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# kognite
|
|
24
|
+
|
|
25
|
+
Python SDK for [Kognite](https://kognite.dev) — long-term memory for AI agents.
|
|
26
|
+
Hybrid semantic + full-text search, LLM memory formation, and a knowledge graph,
|
|
27
|
+
behind one API key. **Zero dependencies** (stdlib only), Python 3.9+.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install kognite
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from kognite import Kognite
|
|
37
|
+
|
|
38
|
+
k = Kognite(api_key="kgn_...")
|
|
39
|
+
|
|
40
|
+
# Store a memory
|
|
41
|
+
k.memories.add("User prefers dark mode", kind="preference")
|
|
42
|
+
|
|
43
|
+
# Hybrid search (semantic + full-text, RRF-fused)
|
|
44
|
+
hits = k.memories.search("ui preferences", limit=5)["results"]
|
|
45
|
+
|
|
46
|
+
# Highest precision (cross-encoder rerank — slower):
|
|
47
|
+
k.memories.search("ui preferences", rerank=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Memory formation (the good stuff)
|
|
51
|
+
|
|
52
|
+
`ingest` runs the pipeline behind Kognite's published LoCoMo results: it stores
|
|
53
|
+
raw messages as episodes **and** extracts atomic facts — pronouns resolved to
|
|
54
|
+
names, relative dates converted to absolute ones. Prefer it over `add` for any
|
|
55
|
+
multi-message context:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
k.memories.ingest([
|
|
59
|
+
{"speaker": "user", "timestamp": "2026-07-15", "content": "I moved to Berlin last month."},
|
|
60
|
+
{"speaker": "assistant", "content": "Noted — how are you finding it?"},
|
|
61
|
+
])
|
|
62
|
+
# → {"raw": 2, "extracted": 1, "stored": 3}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Context assembly
|
|
66
|
+
|
|
67
|
+
A token-budgeted bundle of relevant memories, ready to drop into a prompt:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
ctx = k.context.assemble("user's living situation", token_budget=2000)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Everything else
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
k.memories.get(memory_id)
|
|
77
|
+
k.memories.forget(memory_id) # soft-delete (+ graph cascade)
|
|
78
|
+
k.memories.list(limit=50) # newest-first paging
|
|
79
|
+
k.graph.query("Berlin") # knowledge-graph search
|
|
80
|
+
k.health()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Errors raise `KogniteError` with `.status` (HTTP) and `.code`
|
|
84
|
+
(`validation_failed`, `quota_exceeded`, `not_found`, …). Retries with
|
|
85
|
+
exponential backoff on 429/5xx/network errors are built in.
|
|
86
|
+
|
|
87
|
+
The tenant scope is derived server-side from your API key — the SDK has no
|
|
88
|
+
scope parameter and cannot reach another tenant. Get a key in the
|
|
89
|
+
[dashboard](https://app.kognite.dev), and see the live, judge-audited
|
|
90
|
+
[benchmarks](https://kognite.dev/benchmarks).
|
|
91
|
+
|
|
92
|
+
Self-hosted? Pass `base_url=` to the constructor.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
`smoke_live.py` is a full live-API exercise of every method (safe: runs in the
|
|
97
|
+
key's own scope and forgets everything it creates):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
KOGNITE_API_KEY=kgn_... python smoke_live.py
|
|
101
|
+
```
|
kognite-0.1.0/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# kognite
|
|
2
|
+
|
|
3
|
+
Python SDK for [Kognite](https://kognite.dev) — long-term memory for AI agents.
|
|
4
|
+
Hybrid semantic + full-text search, LLM memory formation, and a knowledge graph,
|
|
5
|
+
behind one API key. **Zero dependencies** (stdlib only), Python 3.9+.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install kognite
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from kognite import Kognite
|
|
15
|
+
|
|
16
|
+
k = Kognite(api_key="kgn_...")
|
|
17
|
+
|
|
18
|
+
# Store a memory
|
|
19
|
+
k.memories.add("User prefers dark mode", kind="preference")
|
|
20
|
+
|
|
21
|
+
# Hybrid search (semantic + full-text, RRF-fused)
|
|
22
|
+
hits = k.memories.search("ui preferences", limit=5)["results"]
|
|
23
|
+
|
|
24
|
+
# Highest precision (cross-encoder rerank — slower):
|
|
25
|
+
k.memories.search("ui preferences", rerank=True)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Memory formation (the good stuff)
|
|
29
|
+
|
|
30
|
+
`ingest` runs the pipeline behind Kognite's published LoCoMo results: it stores
|
|
31
|
+
raw messages as episodes **and** extracts atomic facts — pronouns resolved to
|
|
32
|
+
names, relative dates converted to absolute ones. Prefer it over `add` for any
|
|
33
|
+
multi-message context:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
k.memories.ingest([
|
|
37
|
+
{"speaker": "user", "timestamp": "2026-07-15", "content": "I moved to Berlin last month."},
|
|
38
|
+
{"speaker": "assistant", "content": "Noted — how are you finding it?"},
|
|
39
|
+
])
|
|
40
|
+
# → {"raw": 2, "extracted": 1, "stored": 3}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Context assembly
|
|
44
|
+
|
|
45
|
+
A token-budgeted bundle of relevant memories, ready to drop into a prompt:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
ctx = k.context.assemble("user's living situation", token_budget=2000)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Everything else
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
k.memories.get(memory_id)
|
|
55
|
+
k.memories.forget(memory_id) # soft-delete (+ graph cascade)
|
|
56
|
+
k.memories.list(limit=50) # newest-first paging
|
|
57
|
+
k.graph.query("Berlin") # knowledge-graph search
|
|
58
|
+
k.health()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Errors raise `KogniteError` with `.status` (HTTP) and `.code`
|
|
62
|
+
(`validation_failed`, `quota_exceeded`, `not_found`, …). Retries with
|
|
63
|
+
exponential backoff on 429/5xx/network errors are built in.
|
|
64
|
+
|
|
65
|
+
The tenant scope is derived server-side from your API key — the SDK has no
|
|
66
|
+
scope parameter and cannot reach another tenant. Get a key in the
|
|
67
|
+
[dashboard](https://app.kognite.dev), and see the live, judge-audited
|
|
68
|
+
[benchmarks](https://kognite.dev/benchmarks).
|
|
69
|
+
|
|
70
|
+
Self-hosted? Pass `base_url=` to the constructor.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
`smoke_live.py` is a full live-API exercise of every method (safe: runs in the
|
|
75
|
+
key's own scope and forgets everything it creates):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
KOGNITE_API_KEY=kgn_... python smoke_live.py
|
|
79
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kognite Python SDK — long-term memory for AI agents.
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
from kognite import Kognite
|
|
7
|
+
|
|
8
|
+
k = Kognite(api_key="kgn_your_api_key")
|
|
9
|
+
|
|
10
|
+
k.memories.add("User prefers dark mode", kind="preference")
|
|
11
|
+
hits = k.memories.search("ui preferences")["results"]
|
|
12
|
+
|
|
13
|
+
# Memory formation (episodes + extracted atomic facts):
|
|
14
|
+
k.memories.ingest([
|
|
15
|
+
{"speaker": "user", "timestamp": "2026-07-15", "content": "I moved to Berlin last month."},
|
|
16
|
+
])
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from kognite.client import Kognite, KogniteError
|
|
20
|
+
|
|
21
|
+
__all__ = ["Kognite", "KogniteError"]
|
|
22
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kognite Python SDK — typed client for the Kognite memory API.
|
|
3
|
+
|
|
4
|
+
Zero dependencies (stdlib urllib). The scope (tenant) is derived server-side
|
|
5
|
+
from the API key — there is no scope parameter anywhere in this SDK.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from kognite import Kognite
|
|
10
|
+
|
|
11
|
+
k = Kognite(api_key="kgn_...")
|
|
12
|
+
k.memories.add("User prefers dark mode", kind="preference")
|
|
13
|
+
hits = k.memories.search("ui preferences")["results"]
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import random
|
|
20
|
+
import time
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.parse
|
|
23
|
+
import urllib.request
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
DEFAULT_BASE_URL = "https://api.kognite.dev"
|
|
27
|
+
_RETRYABLE = lambda status: status == 429 or status >= 500 # noqa: E731
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KogniteError(Exception):
|
|
31
|
+
"""Raised on any non-2xx API response.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
status: HTTP status code.
|
|
35
|
+
code: API error code (e.g. "validation_failed", "quota_exceeded", "not_found").
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, status: int, code: str, message: str) -> None:
|
|
39
|
+
self.status = status
|
|
40
|
+
self.code = code
|
|
41
|
+
super().__init__(f"[{status}] {code}: {message}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Kognite:
|
|
45
|
+
"""Kognite API client.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
api_key: Kognite API key (PAT) from the dashboard.
|
|
49
|
+
base_url: Override for self-hosted deployments (default https://api.kognite.dev).
|
|
50
|
+
max_retries: Retries on 429/5xx/network errors (default 3).
|
|
51
|
+
retry_base_delay: Base seconds for exponential backoff (default 0.5).
|
|
52
|
+
timeout: Per-request timeout in seconds (default 30).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
api_key: str,
|
|
58
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
59
|
+
max_retries: int = 3,
|
|
60
|
+
retry_base_delay: float = 0.5,
|
|
61
|
+
timeout: float = 30.0,
|
|
62
|
+
) -> None:
|
|
63
|
+
if not api_key:
|
|
64
|
+
raise ValueError("Kognite: api_key is required")
|
|
65
|
+
self._api_key = api_key
|
|
66
|
+
self._base_url = base_url.rstrip("/")
|
|
67
|
+
self._max_retries = max_retries
|
|
68
|
+
self._retry_base_delay = retry_base_delay
|
|
69
|
+
self._timeout = timeout
|
|
70
|
+
self.memories = _Memories(self)
|
|
71
|
+
self.context = _Context(self)
|
|
72
|
+
self.graph = _Graph(self)
|
|
73
|
+
|
|
74
|
+
# ── HTTP core ────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def _request(
|
|
77
|
+
self,
|
|
78
|
+
method: str,
|
|
79
|
+
path: str,
|
|
80
|
+
body: Optional[Dict[str, Any]] = None,
|
|
81
|
+
query: Optional[Dict[str, Any]] = None,
|
|
82
|
+
) -> Any:
|
|
83
|
+
url = self._base_url + path
|
|
84
|
+
if query:
|
|
85
|
+
filtered = {k: v for k, v in query.items() if v is not None}
|
|
86
|
+
if filtered:
|
|
87
|
+
url += "?" + urllib.parse.urlencode(filtered)
|
|
88
|
+
|
|
89
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
90
|
+
headers = {
|
|
91
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
92
|
+
"Accept": "application/json",
|
|
93
|
+
# A real UA matters: Cloudflare (in front of api.kognite.dev)
|
|
94
|
+
# rejects the default Python-urllib agent with a 403.
|
|
95
|
+
"User-Agent": "kognite-python/0.1.0",
|
|
96
|
+
}
|
|
97
|
+
if data is not None:
|
|
98
|
+
headers["Content-Type"] = "application/json"
|
|
99
|
+
|
|
100
|
+
last_error: Optional[Exception] = None
|
|
101
|
+
for attempt in range(self._max_retries + 1):
|
|
102
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
103
|
+
try:
|
|
104
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as res:
|
|
105
|
+
if res.status == 204:
|
|
106
|
+
return None
|
|
107
|
+
return json.loads(res.read().decode("utf-8"))
|
|
108
|
+
except urllib.error.HTTPError as e:
|
|
109
|
+
status = e.code
|
|
110
|
+
if _RETRYABLE(status) and attempt < self._max_retries:
|
|
111
|
+
time.sleep(random.random() * self._retry_base_delay * (2**attempt))
|
|
112
|
+
continue
|
|
113
|
+
try:
|
|
114
|
+
parsed = json.loads(e.read().decode("utf-8"))
|
|
115
|
+
err = parsed.get("error") or {}
|
|
116
|
+
except Exception:
|
|
117
|
+
err = {}
|
|
118
|
+
raise KogniteError(
|
|
119
|
+
status, err.get("code", "http_error"), err.get("message", f"HTTP {status}")
|
|
120
|
+
) from None
|
|
121
|
+
except (urllib.error.URLError, TimeoutError, OSError) as e:
|
|
122
|
+
last_error = e
|
|
123
|
+
if attempt < self._max_retries:
|
|
124
|
+
time.sleep(random.random() * self._retry_base_delay * (2**attempt))
|
|
125
|
+
continue
|
|
126
|
+
raise KogniteError(0, "network_error", str(last_error))
|
|
127
|
+
|
|
128
|
+
# ── Health ───────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
def health(self) -> Dict[str, Any]:
|
|
131
|
+
"""Liveness check (no auth required by the API)."""
|
|
132
|
+
return self._request("GET", "/api/health")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _Memories:
|
|
136
|
+
"""Memory store — add, search, list, get, forget, batch ingest."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, client: Kognite) -> None:
|
|
139
|
+
self._c = client
|
|
140
|
+
|
|
141
|
+
def add(
|
|
142
|
+
self,
|
|
143
|
+
content: str,
|
|
144
|
+
kind: Optional[str] = None,
|
|
145
|
+
confidence: Optional[float] = None,
|
|
146
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
147
|
+
source: Optional[str] = None,
|
|
148
|
+
idempotency_key: Optional[str] = None,
|
|
149
|
+
) -> Dict[str, Any]:
|
|
150
|
+
"""Store a single memory. Returns ``{"id": ..., "created_at": ...}``."""
|
|
151
|
+
body: Dict[str, Any] = {"content": content}
|
|
152
|
+
if kind is not None:
|
|
153
|
+
body["kind"] = kind
|
|
154
|
+
if confidence is not None:
|
|
155
|
+
body["confidence"] = confidence
|
|
156
|
+
if metadata is not None:
|
|
157
|
+
body["metadata"] = metadata
|
|
158
|
+
if source is not None:
|
|
159
|
+
body["source"] = source
|
|
160
|
+
if idempotency_key is not None:
|
|
161
|
+
body["idempotency_key"] = idempotency_key
|
|
162
|
+
return self._c._request("POST", "/api/memories", body)["data"]
|
|
163
|
+
|
|
164
|
+
def search(
|
|
165
|
+
self,
|
|
166
|
+
query: str,
|
|
167
|
+
limit: Optional[int] = None,
|
|
168
|
+
offset: Optional[int] = None,
|
|
169
|
+
kind: Optional[str] = None,
|
|
170
|
+
min_confidence: Optional[float] = None,
|
|
171
|
+
rerank: bool = False,
|
|
172
|
+
rerank_n: Optional[int] = None,
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""Hybrid semantic + full-text search.
|
|
175
|
+
|
|
176
|
+
Returns ``{"results": [...], "total": int, "capped": bool}``. Pass
|
|
177
|
+
``rerank=True`` for the highest-precision (slower) cross-encoder path.
|
|
178
|
+
"""
|
|
179
|
+
return self._c._request(
|
|
180
|
+
"GET",
|
|
181
|
+
"/api/memories",
|
|
182
|
+
query={
|
|
183
|
+
"q": query,
|
|
184
|
+
"limit": limit,
|
|
185
|
+
"offset": offset,
|
|
186
|
+
"kind": kind,
|
|
187
|
+
"min_confidence": min_confidence,
|
|
188
|
+
"rerank": 1 if rerank else None,
|
|
189
|
+
"rerank_n": rerank_n if rerank else None,
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def list(
|
|
194
|
+
self,
|
|
195
|
+
limit: Optional[int] = None,
|
|
196
|
+
offset: Optional[int] = None,
|
|
197
|
+
kind: Optional[str] = None,
|
|
198
|
+
min_confidence: Optional[float] = None,
|
|
199
|
+
) -> Dict[str, Any]:
|
|
200
|
+
"""Page through memories newest-first. Returns ``{"data": [...], "meta": {...}}``."""
|
|
201
|
+
return self._c._request(
|
|
202
|
+
"GET",
|
|
203
|
+
"/api/memories",
|
|
204
|
+
query={
|
|
205
|
+
"limit": limit,
|
|
206
|
+
"offset": offset,
|
|
207
|
+
"kind": kind,
|
|
208
|
+
"min_confidence": min_confidence,
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def get(self, memory_id: str) -> Dict[str, Any]:
|
|
213
|
+
"""Fetch one memory by id."""
|
|
214
|
+
return self._c._request("GET", f"/api/memories/{urllib.parse.quote(memory_id)}")["data"]
|
|
215
|
+
|
|
216
|
+
def forget(self, memory_id: str) -> None:
|
|
217
|
+
"""Soft-delete a memory (and any graph nodes it alone supported)."""
|
|
218
|
+
self._c._request("DELETE", f"/api/memories/{urllib.parse.quote(memory_id)}")
|
|
219
|
+
|
|
220
|
+
def ingest(
|
|
221
|
+
self,
|
|
222
|
+
messages: List[Dict[str, Any]],
|
|
223
|
+
extract: bool = True,
|
|
224
|
+
store_raw: bool = True,
|
|
225
|
+
) -> Dict[str, Any]:
|
|
226
|
+
"""Batch conversational ingest — the memory-formation pipeline.
|
|
227
|
+
|
|
228
|
+
Stores raw messages as episodes AND extracts atomic facts (names
|
|
229
|
+
resolved, relative dates made absolute). This is what powers Kognite's
|
|
230
|
+
published LoCoMo results; prefer it over ``add`` for any multi-message
|
|
231
|
+
context. Each message: ``{"content": str, "id"?: str, "speaker"?: str,
|
|
232
|
+
"timestamp"?: str}``. Returns ``{"raw": n, "extracted": n, "stored": n}``.
|
|
233
|
+
"""
|
|
234
|
+
return self._c._request(
|
|
235
|
+
"POST",
|
|
236
|
+
"/api/memories/ingest",
|
|
237
|
+
{"messages": messages, "extract": extract, "store_raw": store_raw},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class _Context:
|
|
242
|
+
"""Context assembly — a token-budgeted bundle of relevant memories."""
|
|
243
|
+
|
|
244
|
+
def __init__(self, client: Kognite) -> None:
|
|
245
|
+
self._c = client
|
|
246
|
+
|
|
247
|
+
def assemble(
|
|
248
|
+
self,
|
|
249
|
+
query: str,
|
|
250
|
+
token_budget: Optional[int] = None,
|
|
251
|
+
max_results: Optional[int] = None,
|
|
252
|
+
kinds: Optional[List[str]] = None,
|
|
253
|
+
min_confidence: Optional[float] = None,
|
|
254
|
+
include_skills: bool = False,
|
|
255
|
+
include_graph: bool = True,
|
|
256
|
+
) -> Dict[str, Any]:
|
|
257
|
+
"""Assemble a context bundle for a query, packed under a token budget."""
|
|
258
|
+
body: Dict[str, Any] = {
|
|
259
|
+
"query": query,
|
|
260
|
+
"include_skills": include_skills,
|
|
261
|
+
"include_graph": include_graph,
|
|
262
|
+
}
|
|
263
|
+
if token_budget is not None:
|
|
264
|
+
body["token_budget"] = token_budget
|
|
265
|
+
if max_results is not None:
|
|
266
|
+
body["max_results"] = max_results
|
|
267
|
+
if kinds is not None:
|
|
268
|
+
body["kinds"] = kinds
|
|
269
|
+
if min_confidence is not None:
|
|
270
|
+
body["min_confidence"] = min_confidence
|
|
271
|
+
return self._c._request("POST", "/api/context/assemble", body)["data"]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class _Graph:
|
|
275
|
+
"""Knowledge-graph overview / entity search."""
|
|
276
|
+
|
|
277
|
+
def __init__(self, client: Kognite) -> None:
|
|
278
|
+
self._c = client
|
|
279
|
+
|
|
280
|
+
def query(self, q: Optional[str] = None) -> Dict[str, Any]:
|
|
281
|
+
"""Graph overview; pass ``q`` to also search entities/relationships."""
|
|
282
|
+
return self._c._request("GET", "/api/knowledge-graph", query={"q": q} if q else None)["data"]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kognite
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Kognite — long-term memory for AI agents (hybrid semantic search, memory formation, knowledge graph)
|
|
5
|
+
Author-email: Kognite <hello@kognite.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://kognite.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/global-software-development-eu/memcore-portable-agent-memory-saas
|
|
9
|
+
Keywords: kognite,agent-memory,memory,llm,rag,sdk,ai
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# kognite
|
|
24
|
+
|
|
25
|
+
Python SDK for [Kognite](https://kognite.dev) — long-term memory for AI agents.
|
|
26
|
+
Hybrid semantic + full-text search, LLM memory formation, and a knowledge graph,
|
|
27
|
+
behind one API key. **Zero dependencies** (stdlib only), Python 3.9+.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install kognite
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from kognite import Kognite
|
|
37
|
+
|
|
38
|
+
k = Kognite(api_key="kgn_...")
|
|
39
|
+
|
|
40
|
+
# Store a memory
|
|
41
|
+
k.memories.add("User prefers dark mode", kind="preference")
|
|
42
|
+
|
|
43
|
+
# Hybrid search (semantic + full-text, RRF-fused)
|
|
44
|
+
hits = k.memories.search("ui preferences", limit=5)["results"]
|
|
45
|
+
|
|
46
|
+
# Highest precision (cross-encoder rerank — slower):
|
|
47
|
+
k.memories.search("ui preferences", rerank=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Memory formation (the good stuff)
|
|
51
|
+
|
|
52
|
+
`ingest` runs the pipeline behind Kognite's published LoCoMo results: it stores
|
|
53
|
+
raw messages as episodes **and** extracts atomic facts — pronouns resolved to
|
|
54
|
+
names, relative dates converted to absolute ones. Prefer it over `add` for any
|
|
55
|
+
multi-message context:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
k.memories.ingest([
|
|
59
|
+
{"speaker": "user", "timestamp": "2026-07-15", "content": "I moved to Berlin last month."},
|
|
60
|
+
{"speaker": "assistant", "content": "Noted — how are you finding it?"},
|
|
61
|
+
])
|
|
62
|
+
# → {"raw": 2, "extracted": 1, "stored": 3}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Context assembly
|
|
66
|
+
|
|
67
|
+
A token-budgeted bundle of relevant memories, ready to drop into a prompt:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
ctx = k.context.assemble("user's living situation", token_budget=2000)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Everything else
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
k.memories.get(memory_id)
|
|
77
|
+
k.memories.forget(memory_id) # soft-delete (+ graph cascade)
|
|
78
|
+
k.memories.list(limit=50) # newest-first paging
|
|
79
|
+
k.graph.query("Berlin") # knowledge-graph search
|
|
80
|
+
k.health()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Errors raise `KogniteError` with `.status` (HTTP) and `.code`
|
|
84
|
+
(`validation_failed`, `quota_exceeded`, `not_found`, …). Retries with
|
|
85
|
+
exponential backoff on 429/5xx/network errors are built in.
|
|
86
|
+
|
|
87
|
+
The tenant scope is derived server-side from your API key — the SDK has no
|
|
88
|
+
scope parameter and cannot reach another tenant. Get a key in the
|
|
89
|
+
[dashboard](https://app.kognite.dev), and see the live, judge-audited
|
|
90
|
+
[benchmarks](https://kognite.dev/benchmarks).
|
|
91
|
+
|
|
92
|
+
Self-hosted? Pass `base_url=` to the constructor.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
`smoke_live.py` is a full live-API exercise of every method (safe: runs in the
|
|
97
|
+
key's own scope and forgets everything it creates):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
KOGNITE_API_KEY=kgn_... python smoke_live.py
|
|
101
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kognite
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kognite"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for Kognite — long-term memory for AI agents (hybrid semantic search, memory formation, knowledge graph)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Kognite", email = "hello@kognite.dev"},
|
|
13
|
+
]
|
|
14
|
+
keywords = ["kognite", "agent-memory", "memory", "llm", "rag", "sdk", "ai"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
]
|
|
27
|
+
requires-python = ">=3.9"
|
|
28
|
+
# Zero runtime dependencies — stdlib urllib only.
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://kognite.dev"
|
|
33
|
+
Repository = "https://github.com/global-software-development-eu/memcore-portable-agent-memory-saas"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
include = ["kognite*"]
|
kognite-0.1.0/setup.cfg
ADDED