tinyhumansai 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.
- tinyhumansai-0.1.0/PKG-INFO +133 -0
- tinyhumansai-0.1.0/README.md +119 -0
- tinyhumansai-0.1.0/pyproject.toml +26 -0
- tinyhumansai-0.1.0/scripts/__init__.py +1 -0
- tinyhumansai-0.1.0/scripts/run_act_publish.py +49 -0
- tinyhumansai-0.1.0/setup.cfg +4 -0
- tinyhumansai-0.1.0/tinyhumansai/__init__.py +36 -0
- tinyhumansai-0.1.0/tinyhumansai/async_client.py +197 -0
- tinyhumansai-0.1.0/tinyhumansai/client.py +191 -0
- tinyhumansai-0.1.0/tinyhumansai/types.py +123 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/PKG-INFO +133 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/SOURCES.txt +14 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/dependency_links.txt +1 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/entry_points.txt +2 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/requires.txt +7 -0
- tinyhumansai-0.1.0/tinyhumansai.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinyhumansai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for TinyHumans API - ingest and delete memory
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx>=0.25
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
12
|
+
Requires-Dist: mypy; extra == "dev"
|
|
13
|
+
Requires-Dist: ruff; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# tinyhuman
|
|
16
|
+
|
|
17
|
+
Python SDK for the [TinyHumans API](https://tinyhumans.ai).
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Python ≥ 3.9
|
|
22
|
+
- `httpx >= 0.25`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install tinyhuman
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start (synchronous)
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from tinyhumansai import (
|
|
34
|
+
TinyHumanMemoryClient,
|
|
35
|
+
TinyHumanConfig,
|
|
36
|
+
IngestMemoryRequest,
|
|
37
|
+
ReadMemoryRequest,
|
|
38
|
+
DeleteMemoryRequest,
|
|
39
|
+
MemoryItem,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
client = TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key"))
|
|
43
|
+
|
|
44
|
+
# Ingest (upsert) memory
|
|
45
|
+
result = client.ingest_memory(
|
|
46
|
+
IngestMemoryRequest(
|
|
47
|
+
items=[
|
|
48
|
+
MemoryItem(
|
|
49
|
+
key="user-preference-theme",
|
|
50
|
+
content="User prefers dark mode",
|
|
51
|
+
namespace="preferences",
|
|
52
|
+
metadata={"source": "onboarding"},
|
|
53
|
+
)
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
print(result) # IngestMemoryResponse(ingested=1, updated=0, errors=0)
|
|
58
|
+
|
|
59
|
+
# Read memory
|
|
60
|
+
items = client.read_memory(ReadMemoryRequest(namespace="preferences"))
|
|
61
|
+
print(items.count, items.items)
|
|
62
|
+
|
|
63
|
+
# Delete by key
|
|
64
|
+
client.delete_memory(DeleteMemoryRequest(key="user-preference-theme", namespace="preferences"))
|
|
65
|
+
|
|
66
|
+
# Delete all user memory
|
|
67
|
+
client.delete_memory(DeleteMemoryRequest(delete_all=True))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The client implements the context-manager protocol for deterministic cleanup:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
with TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
74
|
+
result = client.read_memory()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Async usage
|
|
78
|
+
|
|
79
|
+
Use `AsyncTinyHumanMemoryClient` inside `async` code to avoid blocking the
|
|
80
|
+
event loop (e.g. FastAPI, LangGraph async pipelines):
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import asyncio
|
|
84
|
+
from tinyhumansai import AsyncTinyHumanMemoryClient, TinyHumanConfig, ReadMemoryRequest
|
|
85
|
+
|
|
86
|
+
async def main():
|
|
87
|
+
async with AsyncTinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
88
|
+
result = await client.read_memory()
|
|
89
|
+
print(result.items)
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API reference
|
|
95
|
+
|
|
96
|
+
### `TinyHumanMemoryClient(config)` / `AsyncTinyHumanMemoryClient(config)`
|
|
97
|
+
|
|
98
|
+
| Param | Type | Required | Description |
|
|
99
|
+
|-------|------|----------|-------------|
|
|
100
|
+
| `config.token` | `str` | ✓ | JWT or API key |
|
|
101
|
+
| `config.base_url` | `str` | | Override API URL. If not set, uses `ALPHAHUMAN_BASE_URL` from env (e.g. `.env`) or default `https://staging-api.alphahuman.xyz` |
|
|
102
|
+
|
|
103
|
+
### `client.ingest_memory(request)`
|
|
104
|
+
|
|
105
|
+
Upserts memory items. Items are deduplicated by `(namespace, key)`.
|
|
106
|
+
|
|
107
|
+
Returns `IngestMemoryResponse(ingested, updated, errors)`.
|
|
108
|
+
|
|
109
|
+
### `client.read_memory(request?)`
|
|
110
|
+
|
|
111
|
+
Read memory items, optionally filtered by `key`, `keys`, or `namespace`.
|
|
112
|
+
|
|
113
|
+
Returns `ReadMemoryResponse(items, count)`.
|
|
114
|
+
|
|
115
|
+
### `client.delete_memory(request)`
|
|
116
|
+
|
|
117
|
+
Delete memory by `key`, `keys`, or `delete_all=True`, optionally scoped by `namespace`.
|
|
118
|
+
|
|
119
|
+
Returns `DeleteMemoryResponse(deleted)`.
|
|
120
|
+
|
|
121
|
+
## Error handling
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from tinyhumansai import TinyHumanError
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
client.read_memory()
|
|
128
|
+
except TinyHumanError as e:
|
|
129
|
+
print(e.status, str(e))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`TinyHumanError` carries `.status` (HTTP status code) and `.body` (parsed
|
|
133
|
+
response or raw text for non-JSON responses such as gateway errors).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# tinyhuman
|
|
2
|
+
|
|
3
|
+
Python SDK for the [TinyHumans API](https://tinyhumans.ai).
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python ≥ 3.9
|
|
8
|
+
- `httpx >= 0.25`
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install tinyhuman
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start (synchronous)
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from tinyhumansai import (
|
|
20
|
+
TinyHumanMemoryClient,
|
|
21
|
+
TinyHumanConfig,
|
|
22
|
+
IngestMemoryRequest,
|
|
23
|
+
ReadMemoryRequest,
|
|
24
|
+
DeleteMemoryRequest,
|
|
25
|
+
MemoryItem,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
client = TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key"))
|
|
29
|
+
|
|
30
|
+
# Ingest (upsert) memory
|
|
31
|
+
result = client.ingest_memory(
|
|
32
|
+
IngestMemoryRequest(
|
|
33
|
+
items=[
|
|
34
|
+
MemoryItem(
|
|
35
|
+
key="user-preference-theme",
|
|
36
|
+
content="User prefers dark mode",
|
|
37
|
+
namespace="preferences",
|
|
38
|
+
metadata={"source": "onboarding"},
|
|
39
|
+
)
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
print(result) # IngestMemoryResponse(ingested=1, updated=0, errors=0)
|
|
44
|
+
|
|
45
|
+
# Read memory
|
|
46
|
+
items = client.read_memory(ReadMemoryRequest(namespace="preferences"))
|
|
47
|
+
print(items.count, items.items)
|
|
48
|
+
|
|
49
|
+
# Delete by key
|
|
50
|
+
client.delete_memory(DeleteMemoryRequest(key="user-preference-theme", namespace="preferences"))
|
|
51
|
+
|
|
52
|
+
# Delete all user memory
|
|
53
|
+
client.delete_memory(DeleteMemoryRequest(delete_all=True))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The client implements the context-manager protocol for deterministic cleanup:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
with TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
60
|
+
result = client.read_memory()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Async usage
|
|
64
|
+
|
|
65
|
+
Use `AsyncTinyHumanMemoryClient` inside `async` code to avoid blocking the
|
|
66
|
+
event loop (e.g. FastAPI, LangGraph async pipelines):
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from tinyhumansai import AsyncTinyHumanMemoryClient, TinyHumanConfig, ReadMemoryRequest
|
|
71
|
+
|
|
72
|
+
async def main():
|
|
73
|
+
async with AsyncTinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
74
|
+
result = await client.read_memory()
|
|
75
|
+
print(result.items)
|
|
76
|
+
|
|
77
|
+
asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API reference
|
|
81
|
+
|
|
82
|
+
### `TinyHumanMemoryClient(config)` / `AsyncTinyHumanMemoryClient(config)`
|
|
83
|
+
|
|
84
|
+
| Param | Type | Required | Description |
|
|
85
|
+
|-------|------|----------|-------------|
|
|
86
|
+
| `config.token` | `str` | ✓ | JWT or API key |
|
|
87
|
+
| `config.base_url` | `str` | | Override API URL. If not set, uses `ALPHAHUMAN_BASE_URL` from env (e.g. `.env`) or default `https://staging-api.alphahuman.xyz` |
|
|
88
|
+
|
|
89
|
+
### `client.ingest_memory(request)`
|
|
90
|
+
|
|
91
|
+
Upserts memory items. Items are deduplicated by `(namespace, key)`.
|
|
92
|
+
|
|
93
|
+
Returns `IngestMemoryResponse(ingested, updated, errors)`.
|
|
94
|
+
|
|
95
|
+
### `client.read_memory(request?)`
|
|
96
|
+
|
|
97
|
+
Read memory items, optionally filtered by `key`, `keys`, or `namespace`.
|
|
98
|
+
|
|
99
|
+
Returns `ReadMemoryResponse(items, count)`.
|
|
100
|
+
|
|
101
|
+
### `client.delete_memory(request)`
|
|
102
|
+
|
|
103
|
+
Delete memory by `key`, `keys`, or `delete_all=True`, optionally scoped by `namespace`.
|
|
104
|
+
|
|
105
|
+
Returns `DeleteMemoryResponse(deleted)`.
|
|
106
|
+
|
|
107
|
+
## Error handling
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from tinyhumansai import TinyHumanError
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
client.read_memory()
|
|
114
|
+
except TinyHumanError as e:
|
|
115
|
+
print(e.status, str(e))
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`TinyHumanError` carries `.status` (HTTP status code) and `.body` (parsed
|
|
119
|
+
response or raw text for non-JSON responses such as gateway errors).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tinyhumansai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for TinyHumans API - ingest and delete memory"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = ["httpx>=0.25"]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = ["pytest", "pytest-asyncio", "mypy", "ruff"]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
run-act-publish = "scripts.run_act_publish:main"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
include = ["tinyhumansai*", "scripts*"]
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
line-length = 88
|
|
25
|
+
target-version = "py39"
|
|
26
|
+
src = ["tinyhumansai", "alphahuman_memory", "scripts"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Dev script entry points (run-act-publish, etc.)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Run act to test .github/workflows/publish.yml locally with secrets from .act-secrets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _repo_root() -> Path:
|
|
14
|
+
root = Path(__file__).resolve().parent.parent
|
|
15
|
+
if not (root / ".github" / "workflows").exists():
|
|
16
|
+
raise SystemExit("run_act_publish: must run from repo root (has .github/workflows)")
|
|
17
|
+
return root
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None:
|
|
21
|
+
root = _repo_root()
|
|
22
|
+
secrets_file = root / ".act-secrets"
|
|
23
|
+
if not secrets_file.exists():
|
|
24
|
+
print("Missing .act-secrets. Copy act-secrets.example to .act-secrets and add PYPI_API_TOKEN.", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
tag = os.environ.get("ACT_TAG", "v0.1.0")
|
|
28
|
+
event = {"ref": f"refs/tags/{tag}"}
|
|
29
|
+
|
|
30
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
31
|
+
json.dump(event, f)
|
|
32
|
+
event_path = f.name
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
workflow = root / ".github" / "workflows" / "publish.yml"
|
|
36
|
+
cmd = [
|
|
37
|
+
"act",
|
|
38
|
+
"push",
|
|
39
|
+
"-W", str(workflow),
|
|
40
|
+
"--secret-file", str(secrets_file),
|
|
41
|
+
"-e", event_path,
|
|
42
|
+
]
|
|
43
|
+
sys.exit(subprocess.run(cmd, cwd=root).returncode)
|
|
44
|
+
finally:
|
|
45
|
+
os.unlink(event_path, dir_fd=None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""TinyHumans Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .async_client import AsyncTinyHumanMemoryClient, AsyncAlphahumanMemoryClient
|
|
4
|
+
from .client import TinyHumanMemoryClient, AlphahumanMemoryClient
|
|
5
|
+
from .types import (
|
|
6
|
+
TinyHumanConfig,
|
|
7
|
+
TinyHumanError,
|
|
8
|
+
DeleteMemoryRequest,
|
|
9
|
+
DeleteMemoryResponse,
|
|
10
|
+
IngestMemoryRequest,
|
|
11
|
+
IngestMemoryResponse,
|
|
12
|
+
MemoryItem,
|
|
13
|
+
ReadMemoryItem,
|
|
14
|
+
ReadMemoryRequest,
|
|
15
|
+
ReadMemoryResponse,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
# Preferred TinyHuman names
|
|
20
|
+
"TinyHumanMemoryClient",
|
|
21
|
+
"AsyncTinyHumanMemoryClient",
|
|
22
|
+
"TinyHumanConfig",
|
|
23
|
+
"TinyHumanError",
|
|
24
|
+
# Backwards-compatible Alphahuman names
|
|
25
|
+
"AlphahumanMemoryClient",
|
|
26
|
+
"AsyncAlphahumanMemoryClient",
|
|
27
|
+
# Shared types
|
|
28
|
+
"DeleteMemoryRequest",
|
|
29
|
+
"DeleteMemoryResponse",
|
|
30
|
+
"IngestMemoryRequest",
|
|
31
|
+
"IngestMemoryResponse",
|
|
32
|
+
"MemoryItem",
|
|
33
|
+
"ReadMemoryItem",
|
|
34
|
+
"ReadMemoryRequest",
|
|
35
|
+
"ReadMemoryResponse",
|
|
36
|
+
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Async TinyHumans memory client for Python."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .types import (
|
|
11
|
+
TinyHumanConfig,
|
|
12
|
+
TinyHumanError,
|
|
13
|
+
BASE_URL_ENV,
|
|
14
|
+
DEFAULT_BASE_URL,
|
|
15
|
+
DeleteMemoryRequest,
|
|
16
|
+
DeleteMemoryResponse,
|
|
17
|
+
IngestMemoryRequest,
|
|
18
|
+
IngestMemoryResponse,
|
|
19
|
+
ReadMemoryItem,
|
|
20
|
+
ReadMemoryRequest,
|
|
21
|
+
ReadMemoryResponse,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AsyncTinyHumanMemoryClient:
|
|
26
|
+
"""Async client for the TinyHumans memory API.
|
|
27
|
+
|
|
28
|
+
Prefer this in async runtimes (e.g. FastAPI, LangGraph async pipelines)
|
|
29
|
+
to avoid blocking the event loop.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
config: Connection configuration (token required, base_url optional).
|
|
33
|
+
|
|
34
|
+
Example::
|
|
35
|
+
|
|
36
|
+
async with AsyncTinyHumanMemoryClient(TinyHumanConfig(token="...")) as client:
|
|
37
|
+
result = await client.ingest_memory(IngestMemoryRequest(items=[...]))
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: TinyHumanConfig) -> None:
|
|
41
|
+
if not config.token or not config.token.strip():
|
|
42
|
+
raise ValueError("token is required")
|
|
43
|
+
base_url = config.base_url or os.environ.get(BASE_URL_ENV) or DEFAULT_BASE_URL
|
|
44
|
+
self._base_url = base_url.rstrip("/")
|
|
45
|
+
self._token = config.token
|
|
46
|
+
self._http = httpx.AsyncClient(
|
|
47
|
+
base_url=self._base_url,
|
|
48
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
49
|
+
timeout=30,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def close(self) -> None:
|
|
53
|
+
"""Close the underlying HTTP client and release connections."""
|
|
54
|
+
await self._http.aclose()
|
|
55
|
+
|
|
56
|
+
async def __aenter__(self) -> "AsyncAlphahumanMemoryClient":
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
async def __aexit__(self, *_: object) -> None:
|
|
60
|
+
await self.close()
|
|
61
|
+
|
|
62
|
+
async def ingest_memory(self, request: IngestMemoryRequest) -> IngestMemoryResponse:
|
|
63
|
+
"""Ingest (upsert) one or more memory items asynchronously.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
request: The ingestion request containing items to upsert.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Counts of ingested, updated, and errored items.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If items list is empty.
|
|
73
|
+
TinyHumanError: On API errors.
|
|
74
|
+
"""
|
|
75
|
+
if not request.items:
|
|
76
|
+
raise ValueError("items must be a non-empty list")
|
|
77
|
+
|
|
78
|
+
body = {
|
|
79
|
+
"items": [
|
|
80
|
+
{
|
|
81
|
+
"key": item.key,
|
|
82
|
+
"content": item.content,
|
|
83
|
+
"namespace": item.namespace,
|
|
84
|
+
"metadata": item.metadata,
|
|
85
|
+
}
|
|
86
|
+
for item in request.items
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
data = await self._send("POST", "/v1/memory", body)
|
|
90
|
+
return IngestMemoryResponse(
|
|
91
|
+
ingested=data["ingested"],
|
|
92
|
+
updated=data["updated"],
|
|
93
|
+
errors=data["errors"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def read_memory(
|
|
97
|
+
self, request: Optional[ReadMemoryRequest] = None
|
|
98
|
+
) -> ReadMemoryResponse:
|
|
99
|
+
"""Read memory items by key, keys, or namespace asynchronously.
|
|
100
|
+
|
|
101
|
+
Returns all user memory if no filters are provided.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
request: Optional filters for the read.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of matching memory items and count.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
TinyHumanError: On API errors.
|
|
111
|
+
"""
|
|
112
|
+
params: list[tuple[str, str]] = []
|
|
113
|
+
if request:
|
|
114
|
+
if request.key:
|
|
115
|
+
params.append(("key", request.key))
|
|
116
|
+
if request.keys:
|
|
117
|
+
for k in request.keys:
|
|
118
|
+
params.append(("keys[]", k))
|
|
119
|
+
if request.namespace:
|
|
120
|
+
params.append(("namespace", request.namespace))
|
|
121
|
+
|
|
122
|
+
data = await self._get("/v1/memory", params)
|
|
123
|
+
items = [
|
|
124
|
+
ReadMemoryItem(
|
|
125
|
+
key=item["key"],
|
|
126
|
+
content=item["content"],
|
|
127
|
+
namespace=item["namespace"],
|
|
128
|
+
metadata=item.get("metadata", {}),
|
|
129
|
+
created_at=item.get("createdAt", ""),
|
|
130
|
+
updated_at=item.get("updatedAt", ""),
|
|
131
|
+
)
|
|
132
|
+
for item in data["items"]
|
|
133
|
+
]
|
|
134
|
+
return ReadMemoryResponse(items=items, count=data["count"])
|
|
135
|
+
|
|
136
|
+
async def delete_memory(self, request: DeleteMemoryRequest) -> DeleteMemoryResponse:
|
|
137
|
+
"""Delete memory items by key, keys, or delete all asynchronously.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
request: The deletion request specifying what to delete.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Count of deleted items.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValueError: If no deletion target is specified.
|
|
147
|
+
TinyHumanError: On API errors.
|
|
148
|
+
"""
|
|
149
|
+
has_key = isinstance(request.key, str) and len(request.key) > 0
|
|
150
|
+
has_keys = isinstance(request.keys, list) and len(request.keys) > 0
|
|
151
|
+
if not has_key and not has_keys and not request.delete_all:
|
|
152
|
+
raise ValueError('Provide "key", "keys", or set delete_all=True')
|
|
153
|
+
|
|
154
|
+
body: dict[str, Any] = {}
|
|
155
|
+
if request.key is not None:
|
|
156
|
+
body["key"] = request.key
|
|
157
|
+
if request.keys is not None:
|
|
158
|
+
body["keys"] = request.keys
|
|
159
|
+
if request.namespace is not None:
|
|
160
|
+
body["namespace"] = request.namespace
|
|
161
|
+
if request.delete_all:
|
|
162
|
+
body["deleteAll"] = True
|
|
163
|
+
|
|
164
|
+
data = await self._send("DELETE", "/v1/memory", body)
|
|
165
|
+
return DeleteMemoryResponse(deleted=data["deleted"])
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
# Internal helpers
|
|
169
|
+
# ------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
async def _get(self, path: str, params: list[tuple[str, str]]) -> dict[str, Any]:
|
|
172
|
+
response = await self._http.get(path, params=params)
|
|
173
|
+
return self._parse_response(response)
|
|
174
|
+
|
|
175
|
+
async def _send(
|
|
176
|
+
self, method: str, path: str, body: dict[str, Any]
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
response = await self._http.request(method, path, json=body)
|
|
179
|
+
return self._parse_response(response)
|
|
180
|
+
|
|
181
|
+
def _parse_response(self, response: httpx.Response) -> dict[str, Any]:
|
|
182
|
+
try:
|
|
183
|
+
payload = response.json()
|
|
184
|
+
except Exception:
|
|
185
|
+
raise TinyHumanError(
|
|
186
|
+
f"HTTP {response.status_code}: non-JSON response",
|
|
187
|
+
response.status_code,
|
|
188
|
+
response.text,
|
|
189
|
+
)
|
|
190
|
+
if not response.is_success:
|
|
191
|
+
message = payload.get("error", f"HTTP {response.status_code}")
|
|
192
|
+
raise TinyHumanError(message, response.status_code, payload)
|
|
193
|
+
return payload["data"]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# Backwards-compatible alias
|
|
197
|
+
AsyncAlphahumanMemoryClient = AsyncTinyHumanMemoryClient
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""TinyHumans memory client for Python."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .types import (
|
|
11
|
+
TinyHumanConfig,
|
|
12
|
+
TinyHumanError,
|
|
13
|
+
BASE_URL_ENV,
|
|
14
|
+
DEFAULT_BASE_URL,
|
|
15
|
+
DeleteMemoryRequest,
|
|
16
|
+
DeleteMemoryResponse,
|
|
17
|
+
IngestMemoryRequest,
|
|
18
|
+
IngestMemoryResponse,
|
|
19
|
+
ReadMemoryItem,
|
|
20
|
+
ReadMemoryRequest,
|
|
21
|
+
ReadMemoryResponse,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TinyHumanMemoryClient:
|
|
26
|
+
"""Synchronous client for the TinyHumans memory API.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config: Connection configuration (token required, base_url optional).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: TinyHumanConfig) -> None:
|
|
33
|
+
if not config.token or not config.token.strip():
|
|
34
|
+
raise ValueError("token is required")
|
|
35
|
+
base_url = config.base_url or os.environ.get(BASE_URL_ENV) or DEFAULT_BASE_URL
|
|
36
|
+
self._base_url = base_url.rstrip("/")
|
|
37
|
+
self._token = config.token
|
|
38
|
+
self._http = httpx.Client(
|
|
39
|
+
base_url=self._base_url,
|
|
40
|
+
headers={"Authorization": f"Bearer {self._token}"},
|
|
41
|
+
timeout=30,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def close(self) -> None:
|
|
45
|
+
"""Close the underlying HTTP client and release connections."""
|
|
46
|
+
self._http.close()
|
|
47
|
+
|
|
48
|
+
def __enter__(self) -> "AlphahumanMemoryClient":
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def __exit__(self, *_: object) -> None:
|
|
52
|
+
self.close()
|
|
53
|
+
|
|
54
|
+
def ingest_memory(self, request: IngestMemoryRequest) -> IngestMemoryResponse:
|
|
55
|
+
"""Ingest (upsert) one or more memory items.
|
|
56
|
+
|
|
57
|
+
Items are deduped by (namespace, key). If a matching item already
|
|
58
|
+
exists its content and metadata are updated; otherwise a new item
|
|
59
|
+
is created.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
request: The ingestion request containing items to upsert.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Counts of ingested, updated, and errored items.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If items list is empty.
|
|
69
|
+
TinyHumanError: On API errors.
|
|
70
|
+
"""
|
|
71
|
+
if not request.items:
|
|
72
|
+
raise ValueError("items must be a non-empty list")
|
|
73
|
+
|
|
74
|
+
body = {
|
|
75
|
+
"items": [
|
|
76
|
+
{
|
|
77
|
+
"key": item.key,
|
|
78
|
+
"content": item.content,
|
|
79
|
+
"namespace": item.namespace,
|
|
80
|
+
"metadata": item.metadata,
|
|
81
|
+
}
|
|
82
|
+
for item in request.items
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
data = self._send("POST", "/v1/memory", body)
|
|
86
|
+
return IngestMemoryResponse(
|
|
87
|
+
ingested=data["ingested"],
|
|
88
|
+
updated=data["updated"],
|
|
89
|
+
errors=data["errors"],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def read_memory(
|
|
93
|
+
self, request: Optional[ReadMemoryRequest] = None
|
|
94
|
+
) -> ReadMemoryResponse:
|
|
95
|
+
"""Read memory items by key, keys, or namespace.
|
|
96
|
+
|
|
97
|
+
Returns all user memory if no filters are provided.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
request: Optional filters for the read.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of matching memory items and count.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
TinyHumanError: On API errors.
|
|
107
|
+
"""
|
|
108
|
+
params: list[tuple[str, str]] = []
|
|
109
|
+
if request:
|
|
110
|
+
if request.key:
|
|
111
|
+
params.append(("key", request.key))
|
|
112
|
+
if request.keys:
|
|
113
|
+
for k in request.keys:
|
|
114
|
+
params.append(("keys[]", k))
|
|
115
|
+
if request.namespace:
|
|
116
|
+
params.append(("namespace", request.namespace))
|
|
117
|
+
|
|
118
|
+
data = self._get("/v1/memory", params)
|
|
119
|
+
items = [
|
|
120
|
+
ReadMemoryItem(
|
|
121
|
+
key=item["key"],
|
|
122
|
+
content=item["content"],
|
|
123
|
+
namespace=item["namespace"],
|
|
124
|
+
metadata=item.get("metadata", {}),
|
|
125
|
+
created_at=item.get("createdAt", ""),
|
|
126
|
+
updated_at=item.get("updatedAt", ""),
|
|
127
|
+
)
|
|
128
|
+
for item in data["items"]
|
|
129
|
+
]
|
|
130
|
+
return ReadMemoryResponse(items=items, count=data["count"])
|
|
131
|
+
|
|
132
|
+
def delete_memory(self, request: DeleteMemoryRequest) -> DeleteMemoryResponse:
|
|
133
|
+
"""Delete memory items by key, keys, or delete all.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
request: The deletion request specifying what to delete.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Count of deleted items.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: If no deletion target is specified.
|
|
143
|
+
TinyHumanError: On API errors.
|
|
144
|
+
"""
|
|
145
|
+
has_key = isinstance(request.key, str) and len(request.key) > 0
|
|
146
|
+
has_keys = isinstance(request.keys, list) and len(request.keys) > 0
|
|
147
|
+
if not has_key and not has_keys and not request.delete_all:
|
|
148
|
+
raise ValueError('Provide "key", "keys", or set delete_all=True')
|
|
149
|
+
|
|
150
|
+
body: dict[str, Any] = {}
|
|
151
|
+
if request.key is not None:
|
|
152
|
+
body["key"] = request.key
|
|
153
|
+
if request.keys is not None:
|
|
154
|
+
body["keys"] = request.keys
|
|
155
|
+
if request.namespace is not None:
|
|
156
|
+
body["namespace"] = request.namespace
|
|
157
|
+
if request.delete_all:
|
|
158
|
+
body["deleteAll"] = True
|
|
159
|
+
|
|
160
|
+
data = self._send("DELETE", "/v1/memory", body)
|
|
161
|
+
return DeleteMemoryResponse(deleted=data["deleted"])
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
# Internal helpers
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def _get(self, path: str, params: list[tuple[str, str]]) -> dict[str, Any]:
|
|
168
|
+
response = self._http.get(path, params=params)
|
|
169
|
+
return self._parse_response(response)
|
|
170
|
+
|
|
171
|
+
def _send(self, method: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
172
|
+
response = self._http.request(method, path, json=body)
|
|
173
|
+
return self._parse_response(response)
|
|
174
|
+
|
|
175
|
+
def _parse_response(self, response: httpx.Response) -> dict[str, Any]:
|
|
176
|
+
try:
|
|
177
|
+
payload = response.json()
|
|
178
|
+
except Exception:
|
|
179
|
+
raise TinyHumanError(
|
|
180
|
+
f"HTTP {response.status_code}: non-JSON response",
|
|
181
|
+
response.status_code,
|
|
182
|
+
response.text,
|
|
183
|
+
)
|
|
184
|
+
if not response.is_success:
|
|
185
|
+
message = payload.get("error", f"HTTP {response.status_code}")
|
|
186
|
+
raise TinyHumanError(message, response.status_code, payload)
|
|
187
|
+
return payload["data"]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Backwards-compatible alias
|
|
191
|
+
AlphahumanMemoryClient = TinyHumanMemoryClient
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Type definitions for the TinyHumans Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DEFAULT_BASE_URL = "https://staging-api.alphahuman.xyz"
|
|
10
|
+
|
|
11
|
+
# Environment variable for base URL override (e.g. from .env)
|
|
12
|
+
BASE_URL_ENV = "ALPHAHUMAN_BASE_URL"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TinyHumanConfig:
|
|
17
|
+
"""Configuration for the TinyHumans client."""
|
|
18
|
+
|
|
19
|
+
token: str
|
|
20
|
+
"""Bearer token (JWT or API key) for authentication."""
|
|
21
|
+
|
|
22
|
+
base_url: Optional[str] = None
|
|
23
|
+
"""Base URL of the backend. If None, uses ALPHAHUMAN_BASE_URL env var or default staging URL."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class MemoryItem:
|
|
28
|
+
"""A single memory item to ingest."""
|
|
29
|
+
|
|
30
|
+
key: str
|
|
31
|
+
"""Unique key within the namespace (used for upsert / dedup)."""
|
|
32
|
+
|
|
33
|
+
content: str
|
|
34
|
+
"""Memory content text."""
|
|
35
|
+
|
|
36
|
+
namespace: str = "default"
|
|
37
|
+
"""Namespace to scope this item."""
|
|
38
|
+
|
|
39
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
"""Arbitrary metadata."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class IngestMemoryRequest:
|
|
45
|
+
"""Request payload for memory ingestion."""
|
|
46
|
+
|
|
47
|
+
items: list[MemoryItem]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class IngestMemoryResponse:
|
|
52
|
+
"""Response from memory ingestion."""
|
|
53
|
+
|
|
54
|
+
ingested: int
|
|
55
|
+
updated: int
|
|
56
|
+
errors: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ReadMemoryRequest:
|
|
61
|
+
"""Request payload for memory read."""
|
|
62
|
+
|
|
63
|
+
key: Optional[str] = None
|
|
64
|
+
"""Single key to read."""
|
|
65
|
+
|
|
66
|
+
keys: Optional[list[str]] = None
|
|
67
|
+
"""Array of keys to read."""
|
|
68
|
+
|
|
69
|
+
namespace: Optional[str] = None
|
|
70
|
+
"""Namespace scope."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ReadMemoryItem:
|
|
75
|
+
"""A single memory item returned from a read."""
|
|
76
|
+
|
|
77
|
+
key: str
|
|
78
|
+
content: str
|
|
79
|
+
namespace: str
|
|
80
|
+
metadata: dict[str, Any]
|
|
81
|
+
created_at: str
|
|
82
|
+
updated_at: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ReadMemoryResponse:
|
|
87
|
+
"""Response from memory read."""
|
|
88
|
+
|
|
89
|
+
items: list[ReadMemoryItem]
|
|
90
|
+
count: int
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class DeleteMemoryRequest:
|
|
95
|
+
"""Request payload for memory deletion."""
|
|
96
|
+
|
|
97
|
+
key: Optional[str] = None
|
|
98
|
+
"""Single key to delete."""
|
|
99
|
+
|
|
100
|
+
keys: Optional[list[str]] = None
|
|
101
|
+
"""Array of keys to delete."""
|
|
102
|
+
|
|
103
|
+
namespace: Optional[str] = None
|
|
104
|
+
"""Namespace scope (default: 'default')."""
|
|
105
|
+
|
|
106
|
+
delete_all: bool = False
|
|
107
|
+
"""Delete all memory for the user (optionally scoped by namespace)."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class DeleteMemoryResponse:
|
|
112
|
+
"""Response from memory deletion."""
|
|
113
|
+
|
|
114
|
+
deleted: int
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TinyHumanError(Exception):
|
|
118
|
+
"""Error raised by the TinyHumans API."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, message: str, status: int, body: Any = None) -> None:
|
|
121
|
+
super().__init__(message)
|
|
122
|
+
self.status = status
|
|
123
|
+
self.body = body
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinyhumansai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for TinyHumans API - ingest and delete memory
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx>=0.25
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
12
|
+
Requires-Dist: mypy; extra == "dev"
|
|
13
|
+
Requires-Dist: ruff; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# tinyhuman
|
|
16
|
+
|
|
17
|
+
Python SDK for the [TinyHumans API](https://tinyhumans.ai).
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Python ≥ 3.9
|
|
22
|
+
- `httpx >= 0.25`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install tinyhuman
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start (synchronous)
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from tinyhumansai import (
|
|
34
|
+
TinyHumanMemoryClient,
|
|
35
|
+
TinyHumanConfig,
|
|
36
|
+
IngestMemoryRequest,
|
|
37
|
+
ReadMemoryRequest,
|
|
38
|
+
DeleteMemoryRequest,
|
|
39
|
+
MemoryItem,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
client = TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key"))
|
|
43
|
+
|
|
44
|
+
# Ingest (upsert) memory
|
|
45
|
+
result = client.ingest_memory(
|
|
46
|
+
IngestMemoryRequest(
|
|
47
|
+
items=[
|
|
48
|
+
MemoryItem(
|
|
49
|
+
key="user-preference-theme",
|
|
50
|
+
content="User prefers dark mode",
|
|
51
|
+
namespace="preferences",
|
|
52
|
+
metadata={"source": "onboarding"},
|
|
53
|
+
)
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
print(result) # IngestMemoryResponse(ingested=1, updated=0, errors=0)
|
|
58
|
+
|
|
59
|
+
# Read memory
|
|
60
|
+
items = client.read_memory(ReadMemoryRequest(namespace="preferences"))
|
|
61
|
+
print(items.count, items.items)
|
|
62
|
+
|
|
63
|
+
# Delete by key
|
|
64
|
+
client.delete_memory(DeleteMemoryRequest(key="user-preference-theme", namespace="preferences"))
|
|
65
|
+
|
|
66
|
+
# Delete all user memory
|
|
67
|
+
client.delete_memory(DeleteMemoryRequest(delete_all=True))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The client implements the context-manager protocol for deterministic cleanup:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
with TinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
74
|
+
result = client.read_memory()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Async usage
|
|
78
|
+
|
|
79
|
+
Use `AsyncTinyHumanMemoryClient` inside `async` code to avoid blocking the
|
|
80
|
+
event loop (e.g. FastAPI, LangGraph async pipelines):
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import asyncio
|
|
84
|
+
from tinyhumansai import AsyncTinyHumanMemoryClient, TinyHumanConfig, ReadMemoryRequest
|
|
85
|
+
|
|
86
|
+
async def main():
|
|
87
|
+
async with AsyncTinyHumanMemoryClient(TinyHumanConfig(token="your-api-key")) as client:
|
|
88
|
+
result = await client.read_memory()
|
|
89
|
+
print(result.items)
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API reference
|
|
95
|
+
|
|
96
|
+
### `TinyHumanMemoryClient(config)` / `AsyncTinyHumanMemoryClient(config)`
|
|
97
|
+
|
|
98
|
+
| Param | Type | Required | Description |
|
|
99
|
+
|-------|------|----------|-------------|
|
|
100
|
+
| `config.token` | `str` | ✓ | JWT or API key |
|
|
101
|
+
| `config.base_url` | `str` | | Override API URL. If not set, uses `ALPHAHUMAN_BASE_URL` from env (e.g. `.env`) or default `https://staging-api.alphahuman.xyz` |
|
|
102
|
+
|
|
103
|
+
### `client.ingest_memory(request)`
|
|
104
|
+
|
|
105
|
+
Upserts memory items. Items are deduplicated by `(namespace, key)`.
|
|
106
|
+
|
|
107
|
+
Returns `IngestMemoryResponse(ingested, updated, errors)`.
|
|
108
|
+
|
|
109
|
+
### `client.read_memory(request?)`
|
|
110
|
+
|
|
111
|
+
Read memory items, optionally filtered by `key`, `keys`, or `namespace`.
|
|
112
|
+
|
|
113
|
+
Returns `ReadMemoryResponse(items, count)`.
|
|
114
|
+
|
|
115
|
+
### `client.delete_memory(request)`
|
|
116
|
+
|
|
117
|
+
Delete memory by `key`, `keys`, or `delete_all=True`, optionally scoped by `namespace`.
|
|
118
|
+
|
|
119
|
+
Returns `DeleteMemoryResponse(deleted)`.
|
|
120
|
+
|
|
121
|
+
## Error handling
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from tinyhumansai import TinyHumanError
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
client.read_memory()
|
|
128
|
+
except TinyHumanError as e:
|
|
129
|
+
print(e.status, str(e))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`TinyHumanError` carries `.status` (HTTP status code) and `.body` (parsed
|
|
133
|
+
response or raw text for non-JSON responses such as gateway errors).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
scripts/__init__.py
|
|
4
|
+
scripts/run_act_publish.py
|
|
5
|
+
tinyhumansai/__init__.py
|
|
6
|
+
tinyhumansai/async_client.py
|
|
7
|
+
tinyhumansai/client.py
|
|
8
|
+
tinyhumansai/types.py
|
|
9
|
+
tinyhumansai.egg-info/PKG-INFO
|
|
10
|
+
tinyhumansai.egg-info/SOURCES.txt
|
|
11
|
+
tinyhumansai.egg-info/dependency_links.txt
|
|
12
|
+
tinyhumansai.egg-info/entry_points.txt
|
|
13
|
+
tinyhumansai.egg-info/requires.txt
|
|
14
|
+
tinyhumansai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|