rag-plug 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.
- rag_plug-0.1.0/PKG-INFO +122 -0
- rag_plug-0.1.0/README.md +104 -0
- rag_plug-0.1.0/pyproject.toml +17 -0
- rag_plug-0.1.0/ragplug/__init__.py +17 -0
- rag_plug-0.1.0/ragplug/_api.py +45 -0
- rag_plug-0.1.0/ragplug/_error_handler.py +46 -0
- rag_plug-0.1.0/ragplug/_response_parser.py +55 -0
- rag_plug-0.1.0/ragplug/_transport.py +55 -0
- rag_plug-0.1.0/ragplug/_validation.py +24 -0
- rag_plug-0.1.0/ragplug/client.py +121 -0
- rag_plug-0.1.0/ragplug/types.py +37 -0
rag_plug-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rag-plug
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RAG client
|
|
5
|
+
Author: George K
|
|
6
|
+
Author-email: george@dormint.io
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
15
|
+
Requires-Dist: openai-agents (>=0.9.2,<0.10.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# rag-plug
|
|
19
|
+
|
|
20
|
+
Python-клиент для backend RAGPlug API.
|
|
21
|
+
Поддерживает добавление памяти, семантический поиск и удаление записей по ID (sync/async).
|
|
22
|
+
|
|
23
|
+
## Возможности
|
|
24
|
+
- `RagPlug` с типизированными моделями ответов.
|
|
25
|
+
- Аутентификация через `X-API-Key`.
|
|
26
|
+
- Операции в рамках конкретной памяти (`memory_name`).
|
|
27
|
+
- Готовые CLI-скрипты для быстрого add/search.
|
|
28
|
+
|
|
29
|
+
## Требования
|
|
30
|
+
- Python `3.10+`
|
|
31
|
+
- [Poetry](https://python-poetry.org/) (рекомендуется)
|
|
32
|
+
|
|
33
|
+
## Установка
|
|
34
|
+
```bash
|
|
35
|
+
poetry install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Или через pip (локальная разработка):
|
|
39
|
+
```bash
|
|
40
|
+
pip install -e .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Конфигурация
|
|
44
|
+
Создайте `.env` в корне проекта:
|
|
45
|
+
|
|
46
|
+
```env
|
|
47
|
+
API_KEY=your_api_key
|
|
48
|
+
MEMORY_NAME=your_memory_name
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`.env` добавлен в `.gitignore` и не должен попадать в репозиторий.
|
|
52
|
+
|
|
53
|
+
## Использование в Python
|
|
54
|
+
```python
|
|
55
|
+
from ragplug import RagPlug
|
|
56
|
+
|
|
57
|
+
client = RagPlug(api_key="YOUR_API_KEY", default_memory_name="main")
|
|
58
|
+
|
|
59
|
+
item = client.add("Paris is the capital of France", metadata={"source": "docs"})
|
|
60
|
+
result = client.search("capital of France", top_k=3)
|
|
61
|
+
client.delete(item.id)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Async-пример:
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
from ragplug import RagPlug
|
|
68
|
+
|
|
69
|
+
async def main():
|
|
70
|
+
client = RagPlug(api_key="YOUR_API_KEY", default_memory_name="main")
|
|
71
|
+
await client.aadd("Async memory text")
|
|
72
|
+
res = await client.asearch("Async memory")
|
|
73
|
+
print(res.results)
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## CLI-скрипты
|
|
79
|
+
- `scripts/add_memory.py`
|
|
80
|
+
- `scripts/search.py`
|
|
81
|
+
|
|
82
|
+
Пример запуска:
|
|
83
|
+
```bash
|
|
84
|
+
set -a; source .env; set +a
|
|
85
|
+
|
|
86
|
+
python scripts/add_memory.py \
|
|
87
|
+
--api-key "$API_KEY" \
|
|
88
|
+
--memory-name "$MEMORY_NAME" \
|
|
89
|
+
--text "Smoke test memory" \
|
|
90
|
+
--metadata '{"source":"scripts"}'
|
|
91
|
+
|
|
92
|
+
python scripts/search.py \
|
|
93
|
+
--api-key "$API_KEY" \
|
|
94
|
+
--memory-name "$MEMORY_NAME" \
|
|
95
|
+
--query "Smoke test" \
|
|
96
|
+
--top-k 5
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Команды разработки
|
|
100
|
+
```bash
|
|
101
|
+
poetry check # проверка метаданных пакета
|
|
102
|
+
poetry build # сборка sdist + wheel
|
|
103
|
+
python -m compileall ragplug scripts
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Релиз
|
|
107
|
+
CI публикует пакет в PyPI по тегам `v*`.
|
|
108
|
+
Тег должен совпадать с версией в `pyproject.toml`.
|
|
109
|
+
|
|
110
|
+
Пример:
|
|
111
|
+
```bash
|
|
112
|
+
poetry version patch
|
|
113
|
+
git tag v$(poetry version -s)
|
|
114
|
+
git push origin --tags
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Структура проекта
|
|
118
|
+
- `ragplug/client.py`: публичный фасад `RagPlug`.
|
|
119
|
+
- `ragplug/types.py`: модели ответов и `RagPlugError`.
|
|
120
|
+
- `ragplug/_api.py`, `_transport.py`, `_error_handler.py`, `_validation.py`, `_response_parser.py`: внутренние слои клиента.
|
|
121
|
+
- `scripts/`: CLI-утилиты.
|
|
122
|
+
|
rag_plug-0.1.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# rag-plug
|
|
2
|
+
|
|
3
|
+
Python-клиент для backend RAGPlug API.
|
|
4
|
+
Поддерживает добавление памяти, семантический поиск и удаление записей по ID (sync/async).
|
|
5
|
+
|
|
6
|
+
## Возможности
|
|
7
|
+
- `RagPlug` с типизированными моделями ответов.
|
|
8
|
+
- Аутентификация через `X-API-Key`.
|
|
9
|
+
- Операции в рамках конкретной памяти (`memory_name`).
|
|
10
|
+
- Готовые CLI-скрипты для быстрого add/search.
|
|
11
|
+
|
|
12
|
+
## Требования
|
|
13
|
+
- Python `3.10+`
|
|
14
|
+
- [Poetry](https://python-poetry.org/) (рекомендуется)
|
|
15
|
+
|
|
16
|
+
## Установка
|
|
17
|
+
```bash
|
|
18
|
+
poetry install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Или через pip (локальная разработка):
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Конфигурация
|
|
27
|
+
Создайте `.env` в корне проекта:
|
|
28
|
+
|
|
29
|
+
```env
|
|
30
|
+
API_KEY=your_api_key
|
|
31
|
+
MEMORY_NAME=your_memory_name
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`.env` добавлен в `.gitignore` и не должен попадать в репозиторий.
|
|
35
|
+
|
|
36
|
+
## Использование в Python
|
|
37
|
+
```python
|
|
38
|
+
from ragplug import RagPlug
|
|
39
|
+
|
|
40
|
+
client = RagPlug(api_key="YOUR_API_KEY", default_memory_name="main")
|
|
41
|
+
|
|
42
|
+
item = client.add("Paris is the capital of France", metadata={"source": "docs"})
|
|
43
|
+
result = client.search("capital of France", top_k=3)
|
|
44
|
+
client.delete(item.id)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Async-пример:
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from ragplug import RagPlug
|
|
51
|
+
|
|
52
|
+
async def main():
|
|
53
|
+
client = RagPlug(api_key="YOUR_API_KEY", default_memory_name="main")
|
|
54
|
+
await client.aadd("Async memory text")
|
|
55
|
+
res = await client.asearch("Async memory")
|
|
56
|
+
print(res.results)
|
|
57
|
+
|
|
58
|
+
asyncio.run(main())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI-скрипты
|
|
62
|
+
- `scripts/add_memory.py`
|
|
63
|
+
- `scripts/search.py`
|
|
64
|
+
|
|
65
|
+
Пример запуска:
|
|
66
|
+
```bash
|
|
67
|
+
set -a; source .env; set +a
|
|
68
|
+
|
|
69
|
+
python scripts/add_memory.py \
|
|
70
|
+
--api-key "$API_KEY" \
|
|
71
|
+
--memory-name "$MEMORY_NAME" \
|
|
72
|
+
--text "Smoke test memory" \
|
|
73
|
+
--metadata '{"source":"scripts"}'
|
|
74
|
+
|
|
75
|
+
python scripts/search.py \
|
|
76
|
+
--api-key "$API_KEY" \
|
|
77
|
+
--memory-name "$MEMORY_NAME" \
|
|
78
|
+
--query "Smoke test" \
|
|
79
|
+
--top-k 5
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Команды разработки
|
|
83
|
+
```bash
|
|
84
|
+
poetry check # проверка метаданных пакета
|
|
85
|
+
poetry build # сборка sdist + wheel
|
|
86
|
+
python -m compileall ragplug scripts
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Релиз
|
|
90
|
+
CI публикует пакет в PyPI по тегам `v*`.
|
|
91
|
+
Тег должен совпадать с версией в `pyproject.toml`.
|
|
92
|
+
|
|
93
|
+
Пример:
|
|
94
|
+
```bash
|
|
95
|
+
poetry version patch
|
|
96
|
+
git tag v$(poetry version -s)
|
|
97
|
+
git push origin --tags
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Структура проекта
|
|
101
|
+
- `ragplug/client.py`: публичный фасад `RagPlug`.
|
|
102
|
+
- `ragplug/types.py`: модели ответов и `RagPlugError`.
|
|
103
|
+
- `ragplug/_api.py`, `_transport.py`, `_error_handler.py`, `_validation.py`, `_response_parser.py`: внутренние слои клиента.
|
|
104
|
+
- `scripts/`: CLI-утилиты.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "rag-plug"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "RAG client"
|
|
5
|
+
authors = ["George K <george@dormint.io>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{ include = "ragplug" }]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.10"
|
|
11
|
+
openai-agents = "^0.9.2"
|
|
12
|
+
httpx = "^0.28.1"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["poetry-core"]
|
|
17
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .client import RagPlug
|
|
2
|
+
from .types import (
|
|
3
|
+
MemoryDeleteResult,
|
|
4
|
+
MemoryItem,
|
|
5
|
+
RagPlugError,
|
|
6
|
+
SearchResponse,
|
|
7
|
+
SearchResult,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"RagPlug",
|
|
12
|
+
"RagPlugError",
|
|
13
|
+
"MemoryItem",
|
|
14
|
+
"MemoryDeleteResult",
|
|
15
|
+
"SearchResult",
|
|
16
|
+
"SearchResponse",
|
|
17
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
|
|
6
|
+
from ragplug._transport import _HttpTransport
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _RagPlugApi:
|
|
10
|
+
def __init__(self, transport: _HttpTransport) -> None:
|
|
11
|
+
self._transport = transport
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def _segment(value: str) -> str:
|
|
15
|
+
return quote(value, safe="")
|
|
16
|
+
|
|
17
|
+
def version(self) -> Dict[str, Any]:
|
|
18
|
+
return self._transport.request_json("GET", "/version")
|
|
19
|
+
|
|
20
|
+
async def aversion(self) -> Dict[str, Any]:
|
|
21
|
+
return await self._transport.arequest_json("GET", "/version")
|
|
22
|
+
|
|
23
|
+
def add_memory(self, memory_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
24
|
+
path = f"/memory/{self._segment(memory_name)}"
|
|
25
|
+
return self._transport.request_json("POST", path, payload=payload)
|
|
26
|
+
|
|
27
|
+
async def aadd_memory(self, memory_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
28
|
+
path = f"/memory/{self._segment(memory_name)}"
|
|
29
|
+
return await self._transport.arequest_json("POST", path, payload=payload)
|
|
30
|
+
|
|
31
|
+
def delete_memory(self, memory_name: str, item_id: str) -> Dict[str, Any]:
|
|
32
|
+
path = f"/memory/{self._segment(memory_name)}/{self._segment(item_id)}"
|
|
33
|
+
return self._transport.request_json("DELETE", path)
|
|
34
|
+
|
|
35
|
+
async def adelete_memory(self, memory_name: str, item_id: str) -> Dict[str, Any]:
|
|
36
|
+
path = f"/memory/{self._segment(memory_name)}/{self._segment(item_id)}"
|
|
37
|
+
return await self._transport.arequest_json("DELETE", path)
|
|
38
|
+
|
|
39
|
+
def search_memory(self, memory_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
40
|
+
path = f"/search/{self._segment(memory_name)}"
|
|
41
|
+
return self._transport.request_json("POST", path, payload=payload)
|
|
42
|
+
|
|
43
|
+
async def asearch_memory(self, memory_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
44
|
+
path = f"/search/{self._segment(memory_name)}"
|
|
45
|
+
return await self._transport.arequest_json("POST", path, payload=payload)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ragplug.types import RagPlugError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _ErrorHandler:
|
|
11
|
+
@staticmethod
|
|
12
|
+
def raise_for_http_error(response: httpx.Response) -> None:
|
|
13
|
+
if response.is_success:
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
detail = None
|
|
17
|
+
try:
|
|
18
|
+
payload = response.json()
|
|
19
|
+
if isinstance(payload, dict):
|
|
20
|
+
detail = payload.get("detail")
|
|
21
|
+
except ValueError:
|
|
22
|
+
detail = None
|
|
23
|
+
|
|
24
|
+
message = str(detail) if detail else response.text.strip() or "Request failed"
|
|
25
|
+
raise RagPlugError(message=message, status_code=response.status_code)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def raise_network_error(error: httpx.RequestError) -> None:
|
|
29
|
+
raise RagPlugError(f"Network error: {error}") from error
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def parse_json_dict(response: httpx.Response) -> Dict[str, Any]:
|
|
33
|
+
try:
|
|
34
|
+
data = response.json()
|
|
35
|
+
except ValueError as error:
|
|
36
|
+
raise RagPlugError(
|
|
37
|
+
"Invalid JSON response",
|
|
38
|
+
status_code=response.status_code,
|
|
39
|
+
) from error
|
|
40
|
+
|
|
41
|
+
if not isinstance(data, dict):
|
|
42
|
+
raise RagPlugError(
|
|
43
|
+
"Unexpected response format",
|
|
44
|
+
status_code=response.status_code,
|
|
45
|
+
)
|
|
46
|
+
return data
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
|
|
5
|
+
from ragplug.types import (
|
|
6
|
+
MemoryDeleteResult,
|
|
7
|
+
MemoryItem,
|
|
8
|
+
RagPlugError,
|
|
9
|
+
SearchResponse,
|
|
10
|
+
SearchResult,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _ResponseParser:
|
|
15
|
+
@staticmethod
|
|
16
|
+
def version(data: Dict[str, Any]) -> str:
|
|
17
|
+
version_value = data.get("version")
|
|
18
|
+
if version_value is None:
|
|
19
|
+
raise RagPlugError("Unexpected /version response format")
|
|
20
|
+
return str(version_value)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def memory_item(data: Dict[str, Any]) -> MemoryItem:
|
|
24
|
+
return MemoryItem(
|
|
25
|
+
id=str(data.get("id", "")),
|
|
26
|
+
text=str(data.get("text", "")),
|
|
27
|
+
metadata=data.get("metadata") if isinstance(data.get("metadata"), dict) else {},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def memory_delete(data: Dict[str, Any], item_id: str) -> MemoryDeleteResult:
|
|
32
|
+
return MemoryDeleteResult(
|
|
33
|
+
id=str(data.get("id", item_id)),
|
|
34
|
+
deleted=bool(data.get("deleted", False)),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def search(data: Dict[str, Any]) -> SearchResponse:
|
|
39
|
+
raw_results = data.get("results")
|
|
40
|
+
results: List[SearchResult] = []
|
|
41
|
+
|
|
42
|
+
if isinstance(raw_results, list):
|
|
43
|
+
for row in raw_results:
|
|
44
|
+
if not isinstance(row, dict):
|
|
45
|
+
continue
|
|
46
|
+
results.append(
|
|
47
|
+
SearchResult(
|
|
48
|
+
id=str(row.get("id", "")),
|
|
49
|
+
text=str(row.get("text", "")),
|
|
50
|
+
metadata=row.get("metadata") if isinstance(row.get("metadata"), dict) else {},
|
|
51
|
+
score=float(row.get("score", 0.0) or 0.0),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return SearchResponse(query=str(data.get("query", "")), results=results)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ragplug._error_handler import _ErrorHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _HttpTransport:
|
|
11
|
+
def __init__(self, endpoint_url: str, api_key: str, timeout: float) -> None:
|
|
12
|
+
self.endpoint_url = endpoint_url.rstrip("/")
|
|
13
|
+
self.api_key = api_key
|
|
14
|
+
self.timeout = timeout
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def headers(self) -> Dict[str, str]:
|
|
18
|
+
return {
|
|
19
|
+
"X-API-Key": self.api_key,
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
"Accept": "application/json",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def _url(self, path: str) -> str:
|
|
25
|
+
return f"{self.endpoint_url}{path}"
|
|
26
|
+
|
|
27
|
+
def request_json(
|
|
28
|
+
self,
|
|
29
|
+
method: str,
|
|
30
|
+
path: str,
|
|
31
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
try:
|
|
34
|
+
with httpx.Client(timeout=self.timeout, headers=self.headers) as client:
|
|
35
|
+
response = client.request(method, self._url(path), json=payload)
|
|
36
|
+
except httpx.RequestError as error:
|
|
37
|
+
_ErrorHandler.raise_network_error(error)
|
|
38
|
+
|
|
39
|
+
_ErrorHandler.raise_for_http_error(response)
|
|
40
|
+
return _ErrorHandler.parse_json_dict(response)
|
|
41
|
+
|
|
42
|
+
async def arequest_json(
|
|
43
|
+
self,
|
|
44
|
+
method: str,
|
|
45
|
+
path: str,
|
|
46
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
try:
|
|
49
|
+
async with httpx.AsyncClient(timeout=self.timeout, headers=self.headers) as client:
|
|
50
|
+
response = await client.request(method, self._url(path), json=payload)
|
|
51
|
+
except httpx.RequestError as error:
|
|
52
|
+
_ErrorHandler.raise_network_error(error)
|
|
53
|
+
|
|
54
|
+
_ErrorHandler.raise_for_http_error(response)
|
|
55
|
+
return _ErrorHandler.parse_json_dict(response)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Validator:
|
|
7
|
+
@staticmethod
|
|
8
|
+
def require_non_empty(value: str, name: str) -> str:
|
|
9
|
+
if not value or not value.strip():
|
|
10
|
+
raise ValueError(f"{name} must be a non-empty string")
|
|
11
|
+
return value
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def validate_top_k(top_k: int) -> int:
|
|
15
|
+
if top_k < 1 or top_k > 50:
|
|
16
|
+
raise ValueError("top_k must be between 1 and 50")
|
|
17
|
+
return top_k
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def resolve_memory_name(memory_name: Optional[str], default_memory_name: Optional[str]) -> str:
|
|
21
|
+
name = memory_name or default_memory_name
|
|
22
|
+
if not name or not name.strip():
|
|
23
|
+
raise ValueError("memory_name is required (or set default_memory_name in RagPlug)")
|
|
24
|
+
return name
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ragplug._api import _RagPlugApi
|
|
6
|
+
from ragplug._response_parser import _ResponseParser
|
|
7
|
+
from ragplug._transport import _HttpTransport
|
|
8
|
+
from ragplug._validation import _Validator
|
|
9
|
+
from ragplug.types import MemoryDeleteResult, MemoryItem, SearchResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RagPlug:
|
|
13
|
+
endpoint_url = "https://api-ragplug.dormint.io"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
api_key: str,
|
|
18
|
+
endpoint_url: Optional[str] = None,
|
|
19
|
+
default_memory_name: Optional[str] = None,
|
|
20
|
+
timeout: float = 30.0,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.api_key = api_key
|
|
23
|
+
self.endpoint_url = (endpoint_url or self.endpoint_url).rstrip("/")
|
|
24
|
+
self.default_memory_name = default_memory_name
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
|
|
27
|
+
self._transport = _HttpTransport(
|
|
28
|
+
endpoint_url=self.endpoint_url,
|
|
29
|
+
api_key=self.api_key,
|
|
30
|
+
timeout=self.timeout,
|
|
31
|
+
)
|
|
32
|
+
self._api = _RagPlugApi(self._transport)
|
|
33
|
+
|
|
34
|
+
def _memory_name(self, memory_name: Optional[str]) -> str:
|
|
35
|
+
return _Validator.resolve_memory_name(memory_name, self.default_memory_name)
|
|
36
|
+
|
|
37
|
+
def version(self) -> str:
|
|
38
|
+
data = self._api.version()
|
|
39
|
+
return _ResponseParser.version(data)
|
|
40
|
+
|
|
41
|
+
async def aversion(self) -> str:
|
|
42
|
+
data = await self._api.aversion()
|
|
43
|
+
return _ResponseParser.version(data)
|
|
44
|
+
|
|
45
|
+
def add(
|
|
46
|
+
self,
|
|
47
|
+
text: str,
|
|
48
|
+
memory_name: Optional[str] = None,
|
|
49
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
50
|
+
item_id: Optional[str] = None,
|
|
51
|
+
) -> MemoryItem:
|
|
52
|
+
_Validator.require_non_empty(text, "text")
|
|
53
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
54
|
+
|
|
55
|
+
payload: Dict[str, Any] = {
|
|
56
|
+
"text": text,
|
|
57
|
+
"metadata": metadata or {},
|
|
58
|
+
"id": item_id,
|
|
59
|
+
}
|
|
60
|
+
data = self._api.add_memory(resolved_memory_name, payload)
|
|
61
|
+
return _ResponseParser.memory_item(data)
|
|
62
|
+
|
|
63
|
+
async def aadd(
|
|
64
|
+
self,
|
|
65
|
+
text: str,
|
|
66
|
+
memory_name: Optional[str] = None,
|
|
67
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
68
|
+
item_id: Optional[str] = None,
|
|
69
|
+
) -> MemoryItem:
|
|
70
|
+
_Validator.require_non_empty(text, "text")
|
|
71
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
72
|
+
|
|
73
|
+
payload: Dict[str, Any] = {
|
|
74
|
+
"text": text,
|
|
75
|
+
"metadata": metadata or {},
|
|
76
|
+
"id": item_id,
|
|
77
|
+
}
|
|
78
|
+
data = await self._api.aadd_memory(resolved_memory_name, payload)
|
|
79
|
+
return _ResponseParser.memory_item(data)
|
|
80
|
+
|
|
81
|
+
def delete(self, item_id: str, memory_name: Optional[str] = None) -> MemoryDeleteResult:
|
|
82
|
+
_Validator.require_non_empty(item_id, "item_id")
|
|
83
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
84
|
+
|
|
85
|
+
data = self._api.delete_memory(resolved_memory_name, item_id)
|
|
86
|
+
return _ResponseParser.memory_delete(data, item_id)
|
|
87
|
+
|
|
88
|
+
async def adelete(self, item_id: str, memory_name: Optional[str] = None) -> MemoryDeleteResult:
|
|
89
|
+
_Validator.require_non_empty(item_id, "item_id")
|
|
90
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
91
|
+
|
|
92
|
+
data = await self._api.adelete_memory(resolved_memory_name, item_id)
|
|
93
|
+
return _ResponseParser.memory_delete(data, item_id)
|
|
94
|
+
|
|
95
|
+
def search(
|
|
96
|
+
self,
|
|
97
|
+
query: str,
|
|
98
|
+
top_k: int = 5,
|
|
99
|
+
memory_name: Optional[str] = None,
|
|
100
|
+
) -> SearchResponse:
|
|
101
|
+
_Validator.require_non_empty(query, "query")
|
|
102
|
+
_Validator.validate_top_k(top_k)
|
|
103
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
104
|
+
|
|
105
|
+
payload = {"query": query, "top_k": top_k}
|
|
106
|
+
data = self._api.search_memory(resolved_memory_name, payload)
|
|
107
|
+
return _ResponseParser.search(data)
|
|
108
|
+
|
|
109
|
+
async def asearch(
|
|
110
|
+
self,
|
|
111
|
+
query: str,
|
|
112
|
+
top_k: int = 5,
|
|
113
|
+
memory_name: Optional[str] = None,
|
|
114
|
+
) -> SearchResponse:
|
|
115
|
+
_Validator.require_non_empty(query, "query")
|
|
116
|
+
_Validator.validate_top_k(top_k)
|
|
117
|
+
resolved_memory_name = self._memory_name(memory_name)
|
|
118
|
+
|
|
119
|
+
payload = {"query": query, "top_k": top_k}
|
|
120
|
+
data = await self._api.asearch_memory(resolved_memory_name, payload)
|
|
121
|
+
return _ResponseParser.search(data)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class MemoryItem:
|
|
9
|
+
id: str
|
|
10
|
+
text: str
|
|
11
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class MemoryDeleteResult:
|
|
16
|
+
id: str
|
|
17
|
+
deleted: bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class SearchResult:
|
|
22
|
+
id: str
|
|
23
|
+
text: str
|
|
24
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
score: float = 0.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(slots=True)
|
|
29
|
+
class SearchResponse:
|
|
30
|
+
query: str
|
|
31
|
+
results: List[SearchResult] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RagPlugError(RuntimeError):
|
|
35
|
+
def __init__(self, message: str, status_code: Optional[int] = None) -> None:
|
|
36
|
+
super().__init__(message)
|
|
37
|
+
self.status_code = status_code
|