raredaysai 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.
- raredaysai-0.1.0/.gitignore +80 -0
- raredaysai-0.1.0/PKG-INFO +11 -0
- raredaysai-0.1.0/README.md +32 -0
- raredaysai-0.1.0/pyproject.toml +14 -0
- raredaysai-0.1.0/raredaysai/__init__.py +9 -0
- raredaysai-0.1.0/raredaysai/client.py +163 -0
- raredaysai-0.1.0/raredaysai/exceptions.py +5 -0
- raredaysai-0.1.0/raredaysai/types.py +49 -0
- raredaysai-0.1.0/tests/__init__.py +0 -0
- raredaysai-0.1.0/tests/conftest.py +8 -0
- raredaysai-0.1.0/tests/test_client.py +82 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
!.env.example
|
|
36
|
+
|
|
37
|
+
# sst
|
|
38
|
+
.sst/
|
|
39
|
+
sst-env.d.ts
|
|
40
|
+
sst.pyi
|
|
41
|
+
|
|
42
|
+
# opennext
|
|
43
|
+
.open-next/
|
|
44
|
+
|
|
45
|
+
# uv (python reranker)
|
|
46
|
+
uv.lock
|
|
47
|
+
|
|
48
|
+
# pdf data
|
|
49
|
+
/pdfs/
|
|
50
|
+
|
|
51
|
+
# evaluation results
|
|
52
|
+
/eval-results/
|
|
53
|
+
|
|
54
|
+
# vercel
|
|
55
|
+
.vercel
|
|
56
|
+
|
|
57
|
+
# typescript
|
|
58
|
+
*.tsbuildinfo
|
|
59
|
+
next-env.d.ts
|
|
60
|
+
pdfs-full/
|
|
61
|
+
.deploy-config.json
|
|
62
|
+
|
|
63
|
+
# transcript usefulness identifier
|
|
64
|
+
gen_report.output
|
|
65
|
+
gen_report.py
|
|
66
|
+
transcript_assessment.txt
|
|
67
|
+
transcript_assessment_final.txt
|
|
68
|
+
transcript_groups.txt
|
|
69
|
+
transcript_hashes.txt
|
|
70
|
+
transcript_unique_groups.txt
|
|
71
|
+
|
|
72
|
+
# Monorepo
|
|
73
|
+
packages/*/node_modules
|
|
74
|
+
packages/*/.next
|
|
75
|
+
packages/*/dist
|
|
76
|
+
packages/*/.env.local
|
|
77
|
+
.turbo
|
|
78
|
+
|
|
79
|
+
# Local planning (superpowers)
|
|
80
|
+
docs/superpowers/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: raredaysai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RareDaysAI Python SDK — RAG chatbot platform client
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
10
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
11
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# raredaysai
|
|
2
|
+
|
|
3
|
+
Python SDK for [RareDaysAI](https://raredays.ai) — RAG chatbot platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install raredaysai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from raredaysai import RareDaysClient
|
|
15
|
+
|
|
16
|
+
client = RareDaysClient(secret_key="sk_live_xxx")
|
|
17
|
+
|
|
18
|
+
# Chat
|
|
19
|
+
response = client.chat.send("What activities help with dementia?")
|
|
20
|
+
print(response.text)
|
|
21
|
+
|
|
22
|
+
# Sessions (for frontend auth)
|
|
23
|
+
session = client.sessions.create("user_123")
|
|
24
|
+
print(session.token)
|
|
25
|
+
|
|
26
|
+
# Config
|
|
27
|
+
client.config.push({"identity": "You are a helpful bot", "rules": ["Be kind"]})
|
|
28
|
+
|
|
29
|
+
# Sync content
|
|
30
|
+
result = client.connectors.sync("cms")
|
|
31
|
+
print(f"Synced: {result.new_docs} new, {result.updated_docs} updated")
|
|
32
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "raredaysai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "RareDaysAI Python SDK — RAG chatbot platform client"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = ["httpx>=0.27"]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.24", "respx>=0.22"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .client import RareDaysClient, AsyncRareDaysClient
|
|
2
|
+
from .exceptions import RareDaysError
|
|
3
|
+
from .types import ChatResponse, Citation, SessionResponse, ConfigPushResponse, ConfigPullResponse, SyncResult
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"RareDaysClient", "AsyncRareDaysClient", "RareDaysError",
|
|
7
|
+
"ChatResponse", "Citation", "SessionResponse",
|
|
8
|
+
"ConfigPushResponse", "ConfigPullResponse", "SyncResult",
|
|
9
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import httpx
|
|
4
|
+
from .types import ChatResponse, Citation, SessionResponse, ConfigPushResponse, ConfigPullResponse, SyncResult
|
|
5
|
+
from .exceptions import RareDaysError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _ChatNamespace:
|
|
9
|
+
def __init__(self, client: _BaseClient):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def send(self, message: str, conversation_id: Optional[str] = None) -> ChatResponse:
|
|
13
|
+
data = {"messages": [{"role": "user", "content": message}]}
|
|
14
|
+
if conversation_id:
|
|
15
|
+
data["conversationId"] = conversation_id
|
|
16
|
+
resp = self._client._request("POST", "/v1/chat/sync", json=data)
|
|
17
|
+
return ChatResponse(
|
|
18
|
+
text=resp["text"],
|
|
19
|
+
conversation_id=resp.get("conversationId"),
|
|
20
|
+
citations=[Citation(**c) for c in resp.get("citations", [])],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _SessionsNamespace:
|
|
25
|
+
def __init__(self, client: _BaseClient):
|
|
26
|
+
self._client = client
|
|
27
|
+
|
|
28
|
+
def create(self, user_id: str, metadata: Optional[dict] = None, expires_in: Optional[str] = None) -> SessionResponse:
|
|
29
|
+
data: dict = {"userId": user_id}
|
|
30
|
+
if metadata:
|
|
31
|
+
data["metadata"] = metadata
|
|
32
|
+
if expires_in:
|
|
33
|
+
data["expiresIn"] = expires_in
|
|
34
|
+
resp = self._client._request("POST", "/v1/sessions", json=data)
|
|
35
|
+
return SessionResponse(token=resp["token"], expires_at=resp["expiresAt"])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _ConfigNamespace:
|
|
39
|
+
def __init__(self, client: _BaseClient):
|
|
40
|
+
self._client = client
|
|
41
|
+
|
|
42
|
+
def push(self, config: dict) -> ConfigPushResponse:
|
|
43
|
+
resp = self._client._request("POST", "/v1/config/push", json={"config": config})
|
|
44
|
+
return ConfigPushResponse(version=resp["version"], pushed_at=resp["pushedAt"])
|
|
45
|
+
|
|
46
|
+
def pull(self) -> ConfigPullResponse:
|
|
47
|
+
resp = self._client._request("GET", "/v1/config/pull")
|
|
48
|
+
return ConfigPullResponse(config=resp["config"], version=resp["version"], pushed_at=resp["pushedAt"])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _IngestNamespace:
|
|
52
|
+
def __init__(self, client: _BaseClient):
|
|
53
|
+
self._client = client
|
|
54
|
+
|
|
55
|
+
def upload(self, document_id: str, title: str, content: str, source_type: str = "api", metadata: Optional[dict] = None) -> dict:
|
|
56
|
+
data = {"documentId": document_id, "title": title, "content": content, "sourceType": source_type}
|
|
57
|
+
if metadata:
|
|
58
|
+
data["metadata"] = metadata
|
|
59
|
+
return self._client._request("POST", "/v1/ingest", json=data)
|
|
60
|
+
|
|
61
|
+
def delete(self, document_id: str) -> None:
|
|
62
|
+
self._client._request("DELETE", f"/v1/ingest/{document_id}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _ConnectorsNamespace:
|
|
66
|
+
def __init__(self, client: _BaseClient):
|
|
67
|
+
self._client = client
|
|
68
|
+
|
|
69
|
+
def sync(self, connector_id: str) -> SyncResult:
|
|
70
|
+
resp = self._client._request("POST", f"/v1/connectors/{connector_id}/sync")
|
|
71
|
+
r = resp["result"]
|
|
72
|
+
return SyncResult(
|
|
73
|
+
new_docs=r["newDocs"], updated_docs=r["updatedDocs"],
|
|
74
|
+
deleted_docs=r["deletedDocs"], skipped_docs=r["skippedDocs"],
|
|
75
|
+
failed_docs=r["failedDocs"], duration_ms=r["durationMs"],
|
|
76
|
+
errors=r.get("errors", []),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def status(self) -> list[dict]:
|
|
80
|
+
resp = self._client._request("GET", "/v1/connectors")
|
|
81
|
+
return resp["connectors"]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _BaseClient:
|
|
85
|
+
def __init__(self, secret_key: Optional[str] = None, session_token: Optional[str] = None, base_url: str = "https://api.raredays.ai"):
|
|
86
|
+
if not secret_key and not session_token:
|
|
87
|
+
raise ValueError("secret_key or session_token is required")
|
|
88
|
+
self._token = secret_key or session_token
|
|
89
|
+
self._base_url = base_url.rstrip("/")
|
|
90
|
+
self._http = httpx.Client(
|
|
91
|
+
base_url=self._base_url,
|
|
92
|
+
headers={"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"},
|
|
93
|
+
timeout=60,
|
|
94
|
+
)
|
|
95
|
+
self.chat = _ChatNamespace(self)
|
|
96
|
+
self.sessions = _SessionsNamespace(self)
|
|
97
|
+
self.config = _ConfigNamespace(self)
|
|
98
|
+
self.ingest = _IngestNamespace(self)
|
|
99
|
+
self.connectors = _ConnectorsNamespace(self)
|
|
100
|
+
|
|
101
|
+
def _request(self, method: str, path: str, **kwargs) -> dict:
|
|
102
|
+
resp = self._http.request(method, path, **kwargs)
|
|
103
|
+
if resp.status_code >= 400:
|
|
104
|
+
try:
|
|
105
|
+
body = resp.json()
|
|
106
|
+
error = body.get("error", {})
|
|
107
|
+
raise RareDaysError(error.get("code", "unknown"), error.get("message", resp.text), resp.status_code)
|
|
108
|
+
except (ValueError, KeyError):
|
|
109
|
+
raise RareDaysError("unknown", resp.text, resp.status_code)
|
|
110
|
+
if resp.status_code == 204:
|
|
111
|
+
return {}
|
|
112
|
+
return resp.json()
|
|
113
|
+
|
|
114
|
+
def close(self):
|
|
115
|
+
self._http.close()
|
|
116
|
+
|
|
117
|
+
def __enter__(self):
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def __exit__(self, *args):
|
|
121
|
+
self.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RareDaysClient(_BaseClient):
|
|
125
|
+
"""Synchronous RareDaysAI client."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AsyncRareDaysClient:
|
|
130
|
+
"""Async RareDaysAI client using httpx.AsyncClient."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, secret_key: Optional[str] = None, session_token: Optional[str] = None, base_url: str = "https://api.raredays.ai"):
|
|
133
|
+
if not secret_key and not session_token:
|
|
134
|
+
raise ValueError("secret_key or session_token is required")
|
|
135
|
+
self._token = secret_key or session_token
|
|
136
|
+
self._base_url = base_url.rstrip("/")
|
|
137
|
+
self._http = httpx.AsyncClient(
|
|
138
|
+
base_url=self._base_url,
|
|
139
|
+
headers={"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"},
|
|
140
|
+
timeout=60,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def _request(self, method: str, path: str, **kwargs) -> dict:
|
|
144
|
+
resp = await self._http.request(method, path, **kwargs)
|
|
145
|
+
if resp.status_code >= 400:
|
|
146
|
+
try:
|
|
147
|
+
body = resp.json()
|
|
148
|
+
error = body.get("error", {})
|
|
149
|
+
raise RareDaysError(error.get("code", "unknown"), error.get("message", resp.text), resp.status_code)
|
|
150
|
+
except (ValueError, KeyError):
|
|
151
|
+
raise RareDaysError("unknown", resp.text, resp.status_code)
|
|
152
|
+
if resp.status_code == 204:
|
|
153
|
+
return {}
|
|
154
|
+
return resp.json()
|
|
155
|
+
|
|
156
|
+
async def close(self):
|
|
157
|
+
await self._http.aclose()
|
|
158
|
+
|
|
159
|
+
async def __aenter__(self):
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
async def __aexit__(self, *args):
|
|
163
|
+
await self.close()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Citation:
|
|
8
|
+
index: int
|
|
9
|
+
title: str
|
|
10
|
+
section: Optional[str] = None
|
|
11
|
+
page: Optional[int] = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ChatResponse:
|
|
16
|
+
text: str
|
|
17
|
+
conversation_id: Optional[str] = None
|
|
18
|
+
citations: list[Citation] = field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class SessionResponse:
|
|
23
|
+
token: str
|
|
24
|
+
expires_at: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ConfigPushResponse:
|
|
30
|
+
version: int
|
|
31
|
+
pushed_at: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ConfigPullResponse:
|
|
36
|
+
config: dict
|
|
37
|
+
version: int
|
|
38
|
+
pushed_at: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class SyncResult:
|
|
43
|
+
new_docs: int
|
|
44
|
+
updated_docs: int
|
|
45
|
+
deleted_docs: int
|
|
46
|
+
skipped_docs: int
|
|
47
|
+
failed_docs: int
|
|
48
|
+
duration_ms: int
|
|
49
|
+
errors: list[dict] = field(default_factory=list)
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import httpx
|
|
3
|
+
from raredaysai import RareDaysClient, RareDaysError, ChatResponse, SessionResponse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_requires_auth():
|
|
7
|
+
with pytest.raises(ValueError, match="secret_key or session_token"):
|
|
8
|
+
RareDaysClient()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_chat_send(respx_mock):
|
|
12
|
+
respx_mock.post("https://api.raredays.ai/v1/chat/sync").mock(
|
|
13
|
+
return_value=httpx.Response(200, json={
|
|
14
|
+
"text": "Hello world",
|
|
15
|
+
"conversationId": "conv_123",
|
|
16
|
+
"citations": [{"index": 1, "title": "Doc A"}],
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
20
|
+
resp = client.chat.send("Hi")
|
|
21
|
+
assert isinstance(resp, ChatResponse)
|
|
22
|
+
assert resp.text == "Hello world"
|
|
23
|
+
assert resp.conversation_id == "conv_123"
|
|
24
|
+
assert len(resp.citations) == 1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_session_create(respx_mock):
|
|
28
|
+
respx_mock.post("https://api.raredays.ai/v1/sessions").mock(
|
|
29
|
+
return_value=httpx.Response(200, json={"token": "jwt_xxx", "expiresAt": "2026-05-25T00:00:00Z"})
|
|
30
|
+
)
|
|
31
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
32
|
+
resp = client.sessions.create("user_123")
|
|
33
|
+
assert isinstance(resp, SessionResponse)
|
|
34
|
+
assert resp.token == "jwt_xxx"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_config_push(respx_mock):
|
|
38
|
+
respx_mock.post("https://api.raredays.ai/v1/config/push").mock(
|
|
39
|
+
return_value=httpx.Response(200, json={"version": 1, "pushedAt": "2026-05-25T00:00:00Z"})
|
|
40
|
+
)
|
|
41
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
42
|
+
resp = client.config.push({"identity": "Test"})
|
|
43
|
+
assert resp.version == 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_config_pull(respx_mock):
|
|
47
|
+
respx_mock.get("https://api.raredays.ai/v1/config/pull").mock(
|
|
48
|
+
return_value=httpx.Response(200, json={"config": {"identity": "Test"}, "version": 2, "pushedAt": "2026-05-25T00:00:00Z"})
|
|
49
|
+
)
|
|
50
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
51
|
+
resp = client.config.pull()
|
|
52
|
+
assert resp.config["identity"] == "Test"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_error_handling(respx_mock):
|
|
56
|
+
respx_mock.post("https://api.raredays.ai/v1/chat/sync").mock(
|
|
57
|
+
return_value=httpx.Response(401, json={"error": {"code": "unauthorized", "message": "Invalid key"}})
|
|
58
|
+
)
|
|
59
|
+
client = RareDaysClient(secret_key="sk_bad")
|
|
60
|
+
with pytest.raises(RareDaysError) as exc:
|
|
61
|
+
client.chat.send("Hi")
|
|
62
|
+
assert exc.value.status == 401
|
|
63
|
+
assert exc.value.code == "unauthorized"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_connector_sync(respx_mock):
|
|
67
|
+
respx_mock.post("https://api.raredays.ai/v1/connectors/cms/sync").mock(
|
|
68
|
+
return_value=httpx.Response(200, json={"result": {
|
|
69
|
+
"newDocs": 2, "updatedDocs": 1, "deletedDocs": 0,
|
|
70
|
+
"skippedDocs": 5, "failedDocs": 0, "durationMs": 3500, "errors": [],
|
|
71
|
+
}})
|
|
72
|
+
)
|
|
73
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
74
|
+
result = client.connectors.sync("cms")
|
|
75
|
+
assert result.new_docs == 2
|
|
76
|
+
assert result.duration_ms == 3500
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_context_manager():
|
|
80
|
+
client = RareDaysClient(secret_key="sk_test_xxx")
|
|
81
|
+
with client as c:
|
|
82
|
+
assert c is client
|