pluginai 1.0.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.
- pluginai-1.0.0/CHANGELOG.md +30 -0
- pluginai-1.0.0/LICENSE +21 -0
- pluginai-1.0.0/MANIFEST.in +9 -0
- pluginai-1.0.0/PKG-INFO +231 -0
- pluginai-1.0.0/README.md +194 -0
- pluginai-1.0.0/examples/basic_usage.py +30 -0
- pluginai-1.0.0/examples/django_integration.py +86 -0
- pluginai-1.0.0/examples/error_handling.py +50 -0
- pluginai-1.0.0/examples/flask_integration.py +59 -0
- pluginai-1.0.0/examples/multi_turn_conversation.py +42 -0
- pluginai-1.0.0/pluginai/__init__.py +54 -0
- pluginai-1.0.0/pluginai/client.py +347 -0
- pluginai-1.0.0/pluginai/exceptions.py +96 -0
- pluginai-1.0.0/pluginai/models.py +118 -0
- pluginai-1.0.0/pluginai/utils.py +116 -0
- pluginai-1.0.0/pluginai.egg-info/PKG-INFO +231 -0
- pluginai-1.0.0/pluginai.egg-info/SOURCES.txt +23 -0
- pluginai-1.0.0/pluginai.egg-info/dependency_links.txt +1 -0
- pluginai-1.0.0/pluginai.egg-info/requires.txt +7 -0
- pluginai-1.0.0/pluginai.egg-info/top_level.txt +1 -0
- pluginai-1.0.0/pyproject.toml +44 -0
- pluginai-1.0.0/requirements-dev.txt +5 -0
- pluginai-1.0.0/requirements.txt +1 -0
- pluginai-1.0.0/setup.cfg +4 -0
- pluginai-1.0.0/setup.py +41 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `pluginai` Python SDK are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-07-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial public release.
|
|
12
|
+
- `PluginAI` synchronous client with `query()` and `agent_query()` methods.
|
|
13
|
+
- `base_url` defaults to the hosted PluginAI API (`https://api.pluginai.space`),
|
|
14
|
+
so callers only need to pass `api_key` and `workspace`. Override `base_url`
|
|
15
|
+
only when self-hosting.
|
|
16
|
+
- Automatic conversation tracking via `conversation_id`, with
|
|
17
|
+
`reset_conversation()` to start a fresh conversation.
|
|
18
|
+
- Context-manager support (`with PluginAI(...) as client:`).
|
|
19
|
+
- Configurable `timeout`, `max_retries` (transient 502/503/504 only), and an
|
|
20
|
+
`on_error` callback.
|
|
21
|
+
- `QueryResponse`, `Conversation`, and `ConversationMessage` data models.
|
|
22
|
+
- Full exception hierarchy rooted at `PluginAIError`
|
|
23
|
+
(`AuthenticationError`, `QuotaExceededError`, `WorkspaceNotFoundError`,
|
|
24
|
+
`NoDataError`, `ConnectionError`, `TimeoutError`, `RateLimitError`,
|
|
25
|
+
`ServerError`).
|
|
26
|
+
- Input validation for API keys, workspace names, and queries.
|
|
27
|
+
- Answer parsing tolerant of both `respons` and `response` backend keys.
|
|
28
|
+
- Examples for basic usage, multi-turn conversations, error handling, and
|
|
29
|
+
Flask / Django integration.
|
|
30
|
+
- Test suite covering the client, models, and exceptions.
|
pluginai-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Plugin AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pluginai-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pluginai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for PluginAI RAG Platform
|
|
5
|
+
Home-page: https://pluginai.space
|
|
6
|
+
Author: Plugin AI
|
|
7
|
+
Author-email: Plugin AI <support@pluginai.space>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://pluginai.space
|
|
10
|
+
Project-URL: Documentation, https://pluginai.space/docs
|
|
11
|
+
Project-URL: Source, https://pluginai.space
|
|
12
|
+
Keywords: pluginai,rag,chatbot,ai,llm,knowledge-base
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests>=2.28.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=0.10; extra == "dev"
|
|
32
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
|
|
38
|
+
# PluginAI Python SDK
|
|
39
|
+
|
|
40
|
+
Official Python SDK for the **PluginAI** RAG platform. Turn your documents into
|
|
41
|
+
an intelligent, API-accessible assistant, and query it from any Python app in
|
|
42
|
+
under five minutes.
|
|
43
|
+
|
|
44
|
+
- 🐍 Pure Python, one dependency (`requests`)
|
|
45
|
+
- 🔁 Synchronous — works in scripts, notebooks, Flask, Django, FastAPI, anywhere
|
|
46
|
+
- 💬 Built-in multi-turn conversation tracking
|
|
47
|
+
- 🧯 Rich, catchable exception hierarchy
|
|
48
|
+
- ✅ Python 3.8 – 3.12
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install pluginai
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pluginai import PluginAI
|
|
64
|
+
|
|
65
|
+
client = PluginAI(
|
|
66
|
+
api_key="your-api-key",
|
|
67
|
+
workspace="your-workspace-name",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
response = client.query("What is the refund policy?")
|
|
71
|
+
print(response.answer)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`print(response)` also works — a `QueryResponse` stringifies to its answer text.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Multi-turn conversations
|
|
79
|
+
|
|
80
|
+
Every query from the same client shares one `conversation_id`, so the backend
|
|
81
|
+
keeps context across turns. Start over with `reset_conversation()`.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
client.query("What products do you offer?")
|
|
85
|
+
client.query("Which is cheapest?") # remembers the previous turn
|
|
86
|
+
|
|
87
|
+
new_id = client.reset_conversation() # fresh conversation from here on
|
|
88
|
+
client.query("Let's start over.")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use it as a context manager to close the HTTP session automatically:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
with PluginAI(api_key="...", workspace="...") as client:
|
|
95
|
+
print(client.query("Hello").answer)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Agentic queries
|
|
101
|
+
|
|
102
|
+
For complex, multi-step questions, use `agent_query()` — same signature and
|
|
103
|
+
return type as `query()`, but routed through the Agentic RAG pipeline:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
response = client.agent_query(
|
|
107
|
+
"Compare the 2023 and 2024 refund policies and summarize what changed."
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
All constructor parameters:
|
|
116
|
+
|
|
117
|
+
| Parameter | Type | Default | Description |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `api_key` | `str` | **required** | Your workspace API key. |
|
|
120
|
+
| `workspace` | `str` | **required** | The workspace name to query. |
|
|
121
|
+
| `base_url` | `str` | hosted PluginAI API | PluginAI backend URL. Only override if you self-host (trailing slash stripped). |
|
|
122
|
+
| `conversation_id` | `str` | auto-generated | Pass to continue an existing conversation. |
|
|
123
|
+
| `timeout` | `int` | `30` | Per-request timeout, in seconds. |
|
|
124
|
+
| `max_retries` | `int` | `2` | Retries on transient server errors (502/503/504) only. |
|
|
125
|
+
| `on_error` | `callable` | `None` | Called with the exception right before any error is raised. |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## The response object
|
|
130
|
+
|
|
131
|
+
`client.query(...)` returns a `QueryResponse`:
|
|
132
|
+
|
|
133
|
+
| Attribute / property | Type | Description |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `answer` | `str` | The AI-generated answer text. |
|
|
136
|
+
| `status` | `str` | `"success"` or `"no_data"`. |
|
|
137
|
+
| `response_time` | `float` | Seconds the backend took. |
|
|
138
|
+
| `conversation_id` | `str` | The conversation the query belonged to. |
|
|
139
|
+
| `cached` | `bool` | Reserved for future use (always `False`). |
|
|
140
|
+
| `is_success` | `bool` | `True` when `status == "success"`. |
|
|
141
|
+
| `has_data` | `bool` | `False` when `status == "no_data"`. |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Error handling
|
|
146
|
+
|
|
147
|
+
Every SDK error inherits from `PluginAIError`, so you can catch that one base
|
|
148
|
+
class or handle specific cases:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from pluginai import (
|
|
152
|
+
PluginAI,
|
|
153
|
+
AuthenticationError,
|
|
154
|
+
NoDataError,
|
|
155
|
+
QuotaExceededError,
|
|
156
|
+
TimeoutError,
|
|
157
|
+
PluginAIError,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
response = client.query("What is the refund policy?")
|
|
162
|
+
print(response.answer)
|
|
163
|
+
except AuthenticationError:
|
|
164
|
+
print("Check your API key.")
|
|
165
|
+
except NoDataError:
|
|
166
|
+
print("Upload documents to this workspace first.")
|
|
167
|
+
except QuotaExceededError:
|
|
168
|
+
print("Upgrade your plan.")
|
|
169
|
+
except TimeoutError:
|
|
170
|
+
print("Increase the `timeout` parameter.")
|
|
171
|
+
except PluginAIError as exc:
|
|
172
|
+
print(f"Something went wrong: {exc}")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Exception | Raised when |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `PluginAIError` | Base class for all SDK errors. |
|
|
178
|
+
| `AuthenticationError` | HTTP 401/403 — wrong or inactive API key. |
|
|
179
|
+
| `QuotaExceededError` | HTTP 403 mentioning a quota / usage limit. |
|
|
180
|
+
| `WorkspaceNotFoundError` | HTTP 404 — workspace doesn't exist. |
|
|
181
|
+
| `NoDataError` | Backend returned `status == "no_data"`. |
|
|
182
|
+
| `ConnectionError` | Backend unreachable. |
|
|
183
|
+
| `TimeoutError` | Request exceeded `timeout`. |
|
|
184
|
+
| `RateLimitError` | HTTP 429 — too many requests. |
|
|
185
|
+
| `ServerError` | HTTP 5xx — backend error. |
|
|
186
|
+
|
|
187
|
+
Every exception carries `.message` (str) and `.status_code` (int or `None`).
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Framework integration
|
|
192
|
+
|
|
193
|
+
See the [`examples/`](examples/) directory for runnable samples:
|
|
194
|
+
|
|
195
|
+
- [`basic_usage.py`](examples/basic_usage.py)
|
|
196
|
+
- [`multi_turn_conversation.py`](examples/multi_turn_conversation.py)
|
|
197
|
+
- [`error_handling.py`](examples/error_handling.py)
|
|
198
|
+
- [`flask_integration.py`](examples/flask_integration.py)
|
|
199
|
+
- [`django_integration.py`](examples/django_integration.py)
|
|
200
|
+
|
|
201
|
+
**Tip:** create the client **once** at app startup and reuse it — that keeps the
|
|
202
|
+
connection pool warm instead of paying setup cost on every request.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone <repo-url>
|
|
210
|
+
cd pluginai-python
|
|
211
|
+
pip install -e ".[dev]" # or: pip install -r requirements-dev.txt
|
|
212
|
+
pytest tests/
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Publishing to PyPI
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
pip install build twine
|
|
221
|
+
python -m build # builds sdist + wheel into dist/
|
|
222
|
+
pytest tests/ # make sure everything is green
|
|
223
|
+
twine check dist/*
|
|
224
|
+
twine upload dist/* # prompts for your PyPI token
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
[MIT](LICENSE) © Plugin AI
|
pluginai-1.0.0/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# PluginAI Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the **PluginAI** RAG platform. Turn your documents into
|
|
4
|
+
an intelligent, API-accessible assistant, and query it from any Python app in
|
|
5
|
+
under five minutes.
|
|
6
|
+
|
|
7
|
+
- 🐍 Pure Python, one dependency (`requests`)
|
|
8
|
+
- 🔁 Synchronous — works in scripts, notebooks, Flask, Django, FastAPI, anywhere
|
|
9
|
+
- 💬 Built-in multi-turn conversation tracking
|
|
10
|
+
- 🧯 Rich, catchable exception hierarchy
|
|
11
|
+
- ✅ Python 3.8 – 3.12
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install pluginai
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from pluginai import PluginAI
|
|
27
|
+
|
|
28
|
+
client = PluginAI(
|
|
29
|
+
api_key="your-api-key",
|
|
30
|
+
workspace="your-workspace-name",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
response = client.query("What is the refund policy?")
|
|
34
|
+
print(response.answer)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`print(response)` also works — a `QueryResponse` stringifies to its answer text.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Multi-turn conversations
|
|
42
|
+
|
|
43
|
+
Every query from the same client shares one `conversation_id`, so the backend
|
|
44
|
+
keeps context across turns. Start over with `reset_conversation()`.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
client.query("What products do you offer?")
|
|
48
|
+
client.query("Which is cheapest?") # remembers the previous turn
|
|
49
|
+
|
|
50
|
+
new_id = client.reset_conversation() # fresh conversation from here on
|
|
51
|
+
client.query("Let's start over.")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use it as a context manager to close the HTTP session automatically:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
with PluginAI(api_key="...", workspace="...") as client:
|
|
58
|
+
print(client.query("Hello").answer)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Agentic queries
|
|
64
|
+
|
|
65
|
+
For complex, multi-step questions, use `agent_query()` — same signature and
|
|
66
|
+
return type as `query()`, but routed through the Agentic RAG pipeline:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
response = client.agent_query(
|
|
70
|
+
"Compare the 2023 and 2024 refund policies and summarize what changed."
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
All constructor parameters:
|
|
79
|
+
|
|
80
|
+
| Parameter | Type | Default | Description |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `api_key` | `str` | **required** | Your workspace API key. |
|
|
83
|
+
| `workspace` | `str` | **required** | The workspace name to query. |
|
|
84
|
+
| `base_url` | `str` | hosted PluginAI API | PluginAI backend URL. Only override if you self-host (trailing slash stripped). |
|
|
85
|
+
| `conversation_id` | `str` | auto-generated | Pass to continue an existing conversation. |
|
|
86
|
+
| `timeout` | `int` | `30` | Per-request timeout, in seconds. |
|
|
87
|
+
| `max_retries` | `int` | `2` | Retries on transient server errors (502/503/504) only. |
|
|
88
|
+
| `on_error` | `callable` | `None` | Called with the exception right before any error is raised. |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## The response object
|
|
93
|
+
|
|
94
|
+
`client.query(...)` returns a `QueryResponse`:
|
|
95
|
+
|
|
96
|
+
| Attribute / property | Type | Description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `answer` | `str` | The AI-generated answer text. |
|
|
99
|
+
| `status` | `str` | `"success"` or `"no_data"`. |
|
|
100
|
+
| `response_time` | `float` | Seconds the backend took. |
|
|
101
|
+
| `conversation_id` | `str` | The conversation the query belonged to. |
|
|
102
|
+
| `cached` | `bool` | Reserved for future use (always `False`). |
|
|
103
|
+
| `is_success` | `bool` | `True` when `status == "success"`. |
|
|
104
|
+
| `has_data` | `bool` | `False` when `status == "no_data"`. |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Error handling
|
|
109
|
+
|
|
110
|
+
Every SDK error inherits from `PluginAIError`, so you can catch that one base
|
|
111
|
+
class or handle specific cases:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from pluginai import (
|
|
115
|
+
PluginAI,
|
|
116
|
+
AuthenticationError,
|
|
117
|
+
NoDataError,
|
|
118
|
+
QuotaExceededError,
|
|
119
|
+
TimeoutError,
|
|
120
|
+
PluginAIError,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
response = client.query("What is the refund policy?")
|
|
125
|
+
print(response.answer)
|
|
126
|
+
except AuthenticationError:
|
|
127
|
+
print("Check your API key.")
|
|
128
|
+
except NoDataError:
|
|
129
|
+
print("Upload documents to this workspace first.")
|
|
130
|
+
except QuotaExceededError:
|
|
131
|
+
print("Upgrade your plan.")
|
|
132
|
+
except TimeoutError:
|
|
133
|
+
print("Increase the `timeout` parameter.")
|
|
134
|
+
except PluginAIError as exc:
|
|
135
|
+
print(f"Something went wrong: {exc}")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Exception | Raised when |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `PluginAIError` | Base class for all SDK errors. |
|
|
141
|
+
| `AuthenticationError` | HTTP 401/403 — wrong or inactive API key. |
|
|
142
|
+
| `QuotaExceededError` | HTTP 403 mentioning a quota / usage limit. |
|
|
143
|
+
| `WorkspaceNotFoundError` | HTTP 404 — workspace doesn't exist. |
|
|
144
|
+
| `NoDataError` | Backend returned `status == "no_data"`. |
|
|
145
|
+
| `ConnectionError` | Backend unreachable. |
|
|
146
|
+
| `TimeoutError` | Request exceeded `timeout`. |
|
|
147
|
+
| `RateLimitError` | HTTP 429 — too many requests. |
|
|
148
|
+
| `ServerError` | HTTP 5xx — backend error. |
|
|
149
|
+
|
|
150
|
+
Every exception carries `.message` (str) and `.status_code` (int or `None`).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Framework integration
|
|
155
|
+
|
|
156
|
+
See the [`examples/`](examples/) directory for runnable samples:
|
|
157
|
+
|
|
158
|
+
- [`basic_usage.py`](examples/basic_usage.py)
|
|
159
|
+
- [`multi_turn_conversation.py`](examples/multi_turn_conversation.py)
|
|
160
|
+
- [`error_handling.py`](examples/error_handling.py)
|
|
161
|
+
- [`flask_integration.py`](examples/flask_integration.py)
|
|
162
|
+
- [`django_integration.py`](examples/django_integration.py)
|
|
163
|
+
|
|
164
|
+
**Tip:** create the client **once** at app startup and reuse it — that keeps the
|
|
165
|
+
connection pool warm instead of paying setup cost on every request.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git clone <repo-url>
|
|
173
|
+
cd pluginai-python
|
|
174
|
+
pip install -e ".[dev]" # or: pip install -r requirements-dev.txt
|
|
175
|
+
pytest tests/
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Publishing to PyPI
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install build twine
|
|
184
|
+
python -m build # builds sdist + wheel into dist/
|
|
185
|
+
pytest tests/ # make sure everything is green
|
|
186
|
+
twine check dist/*
|
|
187
|
+
twine upload dist/* # prompts for your PyPI token
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
[MIT](LICENSE) © Plugin AI
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Basic usage: send a single query and inspect the response.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
python examples/basic_usage.py
|
|
5
|
+
|
|
6
|
+
Set your real credentials below (or read them from environment variables).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from pluginai import PluginAI
|
|
12
|
+
|
|
13
|
+
API_KEY = os.getenv("PLUGINAI_API_KEY", "your-api-key")
|
|
14
|
+
WORKSPACE = os.getenv("PLUGINAI_WORKSPACE", "your-workspace")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
client = PluginAI(api_key=API_KEY, workspace=WORKSPACE)
|
|
19
|
+
|
|
20
|
+
response = client.query("What is the refund policy?")
|
|
21
|
+
|
|
22
|
+
print("Answer:", response.answer)
|
|
23
|
+
print("Response time:", response.response_time, "seconds")
|
|
24
|
+
print("Success?", response.is_success)
|
|
25
|
+
|
|
26
|
+
client.close()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Django integration: a /chat view backed by a single shared PluginAI client.
|
|
2
|
+
|
|
3
|
+
This is a single-file sketch showing the recommended wiring. In a real project,
|
|
4
|
+
split these pieces into their proper Django files (``apps.py``, ``views.py``,
|
|
5
|
+
``urls.py``). The key idea: create the client **once** in ``AppConfig.ready()``
|
|
6
|
+
and reuse it for every request.
|
|
7
|
+
|
|
8
|
+
--------------------------------------------------------------------------- #
|
|
9
|
+
# myapp/apps.py
|
|
10
|
+
--------------------------------------------------------------------------- #
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from django.apps import AppConfig
|
|
14
|
+
from pluginai import PluginAI
|
|
15
|
+
|
|
16
|
+
class MyAppConfig(AppConfig):
|
|
17
|
+
name = "myapp"
|
|
18
|
+
pluginai_client = None # populated in ready()
|
|
19
|
+
|
|
20
|
+
def ready(self):
|
|
21
|
+
# ready() runs once at startup -- ideal for the shared client.
|
|
22
|
+
MyAppConfig.pluginai_client = PluginAI(
|
|
23
|
+
api_key=os.getenv("PLUGINAI_API_KEY", "your-api-key"),
|
|
24
|
+
workspace=os.getenv("PLUGINAI_WORKSPACE", "your-workspace"),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
--------------------------------------------------------------------------- #
|
|
28
|
+
# myapp/views.py
|
|
29
|
+
--------------------------------------------------------------------------- #
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
|
|
34
|
+
from django.apps import apps
|
|
35
|
+
from django.http import JsonResponse
|
|
36
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
37
|
+
from django.views.decorators.http import require_POST
|
|
38
|
+
|
|
39
|
+
from pluginai import (
|
|
40
|
+
AuthenticationError,
|
|
41
|
+
NoDataError,
|
|
42
|
+
PluginAIError,
|
|
43
|
+
QuotaExceededError,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@csrf_exempt
|
|
48
|
+
@require_POST
|
|
49
|
+
def chat(request):
|
|
50
|
+
"""POST {"message": "..."} -> {"answer": "..."}."""
|
|
51
|
+
try:
|
|
52
|
+
body = json.loads(request.body or b"{}")
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
return JsonResponse({"error": "Invalid JSON body."}, status=400)
|
|
55
|
+
|
|
56
|
+
message = body.get("message")
|
|
57
|
+
if not message:
|
|
58
|
+
return JsonResponse({"error": "Field 'message' is required."}, status=400)
|
|
59
|
+
|
|
60
|
+
# Pull the shared client created in AppConfig.ready().
|
|
61
|
+
client = apps.get_app_config("myapp").pluginai_client
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = client.query(message)
|
|
65
|
+
return JsonResponse({"answer": response.answer})
|
|
66
|
+
|
|
67
|
+
except NoDataError:
|
|
68
|
+
return JsonResponse({"error": "No documents indexed."}, status=404)
|
|
69
|
+
except AuthenticationError:
|
|
70
|
+
return JsonResponse({"error": "Authentication failed."}, status=401)
|
|
71
|
+
except QuotaExceededError:
|
|
72
|
+
return JsonResponse({"error": "Usage quota exceeded."}, status=429)
|
|
73
|
+
except PluginAIError as exc:
|
|
74
|
+
return JsonResponse({"error": str(exc)}, status=502)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# --------------------------------------------------------------------------- #
|
|
78
|
+
# myapp/urls.py
|
|
79
|
+
# --------------------------------------------------------------------------- #
|
|
80
|
+
#
|
|
81
|
+
# from django.urls import path
|
|
82
|
+
# from . import views
|
|
83
|
+
#
|
|
84
|
+
# urlpatterns = [
|
|
85
|
+
# path("chat", views.chat, name="chat"),
|
|
86
|
+
# ]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Error handling: catch specific exceptions and react appropriately.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
python examples/error_handling.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from pluginai import (
|
|
10
|
+
AuthenticationError,
|
|
11
|
+
NoDataError,
|
|
12
|
+
PluginAI,
|
|
13
|
+
PluginAIError,
|
|
14
|
+
QuotaExceededError,
|
|
15
|
+
TimeoutError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
API_KEY = os.getenv("PLUGINAI_API_KEY", "your-api-key")
|
|
19
|
+
WORKSPACE = os.getenv("PLUGINAI_WORKSPACE", "your-workspace")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> None:
|
|
23
|
+
client = PluginAI(api_key=API_KEY, workspace=WORKSPACE)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
response = client.query("What is the refund policy?")
|
|
27
|
+
print("Answer:", response.answer)
|
|
28
|
+
|
|
29
|
+
except AuthenticationError:
|
|
30
|
+
print("Your API key is invalid or inactive -- double-check it.")
|
|
31
|
+
|
|
32
|
+
except NoDataError:
|
|
33
|
+
print("No documents found -- upload some to this workspace first.")
|
|
34
|
+
|
|
35
|
+
except QuotaExceededError:
|
|
36
|
+
print("You've hit your plan's usage limit -- consider upgrading.")
|
|
37
|
+
|
|
38
|
+
except TimeoutError:
|
|
39
|
+
print("The request timed out -- try increasing the `timeout` parameter.")
|
|
40
|
+
|
|
41
|
+
except PluginAIError as exc:
|
|
42
|
+
# Catch-all for any other SDK error (server errors, rate limits, etc.).
|
|
43
|
+
print(f"Something went wrong: {exc}")
|
|
44
|
+
|
|
45
|
+
finally:
|
|
46
|
+
client.close()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|