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.
@@ -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.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include requirements.txt
5
+ include requirements-dev.txt
6
+ recursive-include examples *.py
7
+ recursive-exclude tests *
8
+ global-exclude __pycache__
9
+ global-exclude *.py[cod]
@@ -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
@@ -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()