liminal-sdk-python 2024.3.4__tar.gz → 2024.3.5__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.
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/PKG-INFO +95 -21
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/README.md +94 -20
- liminal_sdk_python-2024.3.5/liminal/auth/__init__.py +17 -0
- liminal_sdk_python-2024.3.5/liminal/auth/microsoft/__init__.py +1 -0
- liminal_sdk_python-2024.3.4/liminal/endpoints/auth/__init__.py → liminal_sdk_python-2024.3.5/liminal/auth/microsoft/device_code_flow.py +5 -5
- {liminal_sdk_python-2024.3.4/liminal/endpoints/auth → liminal_sdk_python-2024.3.5/liminal/auth/microsoft}/models.py +3 -19
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/client.py +4 -17
- liminal_sdk_python-2024.3.5/liminal/endpoints/llm/models.py +79 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/models.py +28 -28
- liminal_sdk_python-2024.3.5/liminal/endpoints/thread/models.py +61 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/model.py +1 -1
- liminal_sdk_python-2024.3.5/pyproject.toml +115 -0
- liminal_sdk_python-2024.3.4/liminal/endpoints/llm/models.py +0 -46
- liminal_sdk_python-2024.3.4/liminal/endpoints/thread/models.py +0 -31
- liminal_sdk_python-2024.3.4/pyproject.toml +0 -441
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/LICENSE +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/const.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/llm/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/thread/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/errors.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/typing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: liminal-sdk-python
|
|
3
|
-
Version: 2024.3.
|
|
3
|
+
Version: 2024.3.5
|
|
4
4
|
Summary: The Liminal SDK for Python
|
|
5
5
|
Home-page: https://github.com/liminal-ai-security/liminal-sdk-python
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
# Liminal Python SDK
|
|
27
27
|
|
|
28
|
+
[![CI][ci-badge]][ci]
|
|
28
29
|
[![PyPI][pypi-badge]][pypi]
|
|
29
30
|
[![Version][version-badge]][version]
|
|
30
31
|
[![License][license-badge]][license]
|
|
@@ -34,7 +35,19 @@ for interacting with the Liminal API.
|
|
|
34
35
|
|
|
35
36
|
- [Installation](#installation)
|
|
36
37
|
- [Python Versions](#python-versions)
|
|
37
|
-
- [
|
|
38
|
+
- [Quickstart](#quickstart)
|
|
39
|
+
- [Initial Authentication](#initial-authentication)
|
|
40
|
+
- [Microsoft Entra ID](#microsoft-entra-id)
|
|
41
|
+
- [Device Code Flow](#device-code-flow)
|
|
42
|
+
- [Ongoing Authentication](#ongoing-authentication)
|
|
43
|
+
- [Manually Interacting with the Refresh Token](#manually-interacting-with-the-refresh-token)
|
|
44
|
+
- [Creating a Liminal Client from a Stored Refresh Token](#creating-a-liminal-client-from-a-stored-refresh-token)
|
|
45
|
+
- [Endpoints](#endpoints)
|
|
46
|
+
- [Getting Model Instances](#getting-model-instances)
|
|
47
|
+
- [Managing Threads](#managing-threads)
|
|
48
|
+
- [Submitting Prompts](#submitting-prompts)
|
|
49
|
+
- [Connection Pooling](#connection-pooling)
|
|
50
|
+
- [Running Examples](#running-examples)
|
|
38
51
|
- [Contributing](#contributing)
|
|
39
52
|
|
|
40
53
|
# Installation
|
|
@@ -59,16 +72,16 @@ API object is easy:
|
|
|
59
72
|
import asyncio
|
|
60
73
|
|
|
61
74
|
from liminal import Client
|
|
62
|
-
from liminal.
|
|
75
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
63
76
|
|
|
64
77
|
|
|
65
78
|
async def main() -> None:
|
|
66
79
|
"""Create the aiohttp session and run the example."""
|
|
67
80
|
# Create an auth provider to authenticate the user:
|
|
68
|
-
|
|
81
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
69
82
|
|
|
70
83
|
# Create the liminal SDK instance:
|
|
71
|
-
liminal = Client(
|
|
84
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
72
85
|
|
|
73
86
|
|
|
74
87
|
asyncio.run(main())
|
|
@@ -86,13 +99,15 @@ the following auth providers are supported:
|
|
|
86
99
|
|
|
87
100
|
## Microsoft Entra ID
|
|
88
101
|
|
|
89
|
-
|
|
102
|
+
### Device Code Flow
|
|
103
|
+
|
|
104
|
+
This authentication process with Microsoft Entra ID involves an
|
|
90
105
|
[OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
|
|
91
106
|
to start your app, retrieve a device code from the logs produced by this SDK, and
|
|
92
107
|
provide that code to Microsoft via a web browser. Once you complete the login process,
|
|
93
108
|
the SDK will be authenticated for use with your Liminal instance.
|
|
94
109
|
|
|
95
|
-
|
|
110
|
+
To authenticate with this flow, you will need an Entra ID client and tenant ID:
|
|
96
111
|
|
|
97
112
|
- Log into your [Azure portal][azure-portal].
|
|
98
113
|
- Navigate to `Microsoft Entra ID`.
|
|
@@ -101,8 +116,6 @@ the SDK will be authenticated for use with your Liminal instance.
|
|
|
101
116
|
- In the `Overview` of the registration, look for the `Application (client) ID` and
|
|
102
117
|
`Directory (tenant) ID` values.
|
|
103
118
|
|
|
104
|
-
### Authenticating Against Entra ID
|
|
105
|
-
|
|
106
119
|
With a client ID and tenant ID, you can create a Liminal client object and authenticate
|
|
107
120
|
it:
|
|
108
121
|
|
|
@@ -110,16 +123,16 @@ it:
|
|
|
110
123
|
import asyncio
|
|
111
124
|
|
|
112
125
|
from liminal import Client
|
|
113
|
-
from liminal.
|
|
126
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
114
127
|
|
|
115
128
|
|
|
116
129
|
async def main() -> None:
|
|
117
130
|
"""Create the aiohttp session and run the example."""
|
|
118
131
|
# Create an auth provider to authenticate the user:
|
|
119
|
-
|
|
132
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
120
133
|
|
|
121
134
|
# Create the liminal SDK instance and authenticate it:
|
|
122
|
-
liminal = Client(
|
|
135
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
123
136
|
await liminal.authenticate_from_auth_provider()
|
|
124
137
|
|
|
125
138
|
|
|
@@ -161,13 +174,13 @@ callback at any time.
|
|
|
161
174
|
import asyncio
|
|
162
175
|
|
|
163
176
|
from liminal import Client
|
|
164
|
-
from liminal.
|
|
177
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
165
178
|
|
|
166
179
|
|
|
167
180
|
async def main() -> None:
|
|
168
181
|
"""Create the aiohttp session and run the example."""
|
|
169
182
|
# Create an auth provider to authenticate the user:
|
|
170
|
-
|
|
183
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
171
184
|
|
|
172
185
|
# Create the liminal SDK instance and authenticate it:
|
|
173
186
|
liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
@@ -189,7 +202,32 @@ async def main() -> None:
|
|
|
189
202
|
asyncio.run(main())
|
|
190
203
|
```
|
|
191
204
|
|
|
192
|
-
|
|
205
|
+
## Creating a Liminal Client from a Stored Refresh Token
|
|
206
|
+
|
|
207
|
+
Assuming you have a stored refresh token (collected from the callback process shown
|
|
208
|
+
above), it is simple to create a new Limina client using that token:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
import asyncio
|
|
212
|
+
|
|
213
|
+
from liminal import Client
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def main() -> None:
|
|
217
|
+
"""Create the aiohttp session and run the example."""
|
|
218
|
+
# Retrieve your stored refresh_token:
|
|
219
|
+
refresh_token = "12345"
|
|
220
|
+
|
|
221
|
+
# Create the client:
|
|
222
|
+
liminal = await client.authenticate_from_refresh_token(refresh_token=refresh_token)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
asyncio.run(main())
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
# Endpoints
|
|
229
|
+
|
|
230
|
+
## Getting Model Instances
|
|
193
231
|
|
|
194
232
|
Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
|
|
195
233
|
instance." The SDK provides several methods to interact with model instances:
|
|
@@ -198,7 +236,7 @@ instance." The SDK provides several methods to interact with model instances:
|
|
|
198
236
|
import asyncio
|
|
199
237
|
|
|
200
238
|
from liminal import Client
|
|
201
|
-
from liminal.
|
|
239
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
202
240
|
|
|
203
241
|
|
|
204
242
|
async def main() -> None:
|
|
@@ -216,7 +254,7 @@ async def main() -> None:
|
|
|
216
254
|
asyncio.run(main())
|
|
217
255
|
```
|
|
218
256
|
|
|
219
|
-
|
|
257
|
+
## Managing Threads
|
|
220
258
|
|
|
221
259
|
Threads are conversations with an LLM instance:
|
|
222
260
|
|
|
@@ -224,7 +262,7 @@ Threads are conversations with an LLM instance:
|
|
|
224
262
|
import asyncio
|
|
225
263
|
|
|
226
264
|
from liminal import Client
|
|
227
|
-
from liminal.
|
|
265
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
228
266
|
|
|
229
267
|
|
|
230
268
|
async def main() -> None:
|
|
@@ -253,7 +291,7 @@ async def main() -> None:
|
|
|
253
291
|
asyncio.run(main())
|
|
254
292
|
```
|
|
255
293
|
|
|
256
|
-
|
|
294
|
+
## Submitting Prompts
|
|
257
295
|
|
|
258
296
|
Submitting prompts is easy:
|
|
259
297
|
|
|
@@ -261,7 +299,7 @@ Submitting prompts is easy:
|
|
|
261
299
|
import asyncio
|
|
262
300
|
|
|
263
301
|
from liminal import Client
|
|
264
|
-
from liminal.
|
|
302
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
265
303
|
|
|
266
304
|
|
|
267
305
|
async def main() -> None:
|
|
@@ -300,6 +338,40 @@ async def main() -> None:
|
|
|
300
338
|
asyncio.run(main())
|
|
301
339
|
```
|
|
302
340
|
|
|
341
|
+
# Connection Pooling
|
|
342
|
+
|
|
343
|
+
By default, the library creates a new connection to the Liminal API server with each
|
|
344
|
+
coroutine. If you are calling a large number of coroutines (or merely want to squeeze
|
|
345
|
+
out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
|
|
346
|
+
used for connection pooling:
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
import asyncio
|
|
350
|
+
|
|
351
|
+
from liminal import Client
|
|
352
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
async def main() -> None:
|
|
356
|
+
# Create an auth provider to authenticate the user:
|
|
357
|
+
microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
358
|
+
|
|
359
|
+
# Create the liminal SDK instance with a shared HTTPX AsyncClient:
|
|
360
|
+
async with httpx.AsyncClient() as client:
|
|
361
|
+
liminal = Client(
|
|
362
|
+
microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Get to work!
|
|
366
|
+
# ...
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
asyncio.run(main())
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Check out the examples, the tests, and the source files themselves for method
|
|
373
|
+
signatures and more examples.
|
|
374
|
+
|
|
303
375
|
# Running Examples
|
|
304
376
|
|
|
305
377
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -322,7 +394,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
322
394
|
2. [Fork the repository][fork].
|
|
323
395
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
324
396
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
325
|
-
5. Install the dev environment:
|
|
397
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
326
398
|
6. Code your new feature or bug fix on a new branch.
|
|
327
399
|
7. Write tests that cover your new functionality.
|
|
328
400
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -330,10 +402,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
330
402
|
10. Submit a pull request!
|
|
331
403
|
|
|
332
404
|
[azure-portal]: https://portal.azure.com
|
|
405
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
|
|
333
406
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
334
407
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
335
408
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
336
409
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
410
|
+
[httpx]: https://www.python-httpx.org/
|
|
337
411
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
338
412
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
339
413
|
[license]: https://github.com/liminal-ai-security/liminal-sdk-python/blob/main/LICENSE
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Liminal Python SDK
|
|
2
2
|
|
|
3
|
+
[![CI][ci-badge]][ci]
|
|
3
4
|
[![PyPI][pypi-badge]][pypi]
|
|
4
5
|
[![Version][version-badge]][version]
|
|
5
6
|
[![License][license-badge]][license]
|
|
@@ -9,7 +10,19 @@ for interacting with the Liminal API.
|
|
|
9
10
|
|
|
10
11
|
- [Installation](#installation)
|
|
11
12
|
- [Python Versions](#python-versions)
|
|
12
|
-
- [
|
|
13
|
+
- [Quickstart](#quickstart)
|
|
14
|
+
- [Initial Authentication](#initial-authentication)
|
|
15
|
+
- [Microsoft Entra ID](#microsoft-entra-id)
|
|
16
|
+
- [Device Code Flow](#device-code-flow)
|
|
17
|
+
- [Ongoing Authentication](#ongoing-authentication)
|
|
18
|
+
- [Manually Interacting with the Refresh Token](#manually-interacting-with-the-refresh-token)
|
|
19
|
+
- [Creating a Liminal Client from a Stored Refresh Token](#creating-a-liminal-client-from-a-stored-refresh-token)
|
|
20
|
+
- [Endpoints](#endpoints)
|
|
21
|
+
- [Getting Model Instances](#getting-model-instances)
|
|
22
|
+
- [Managing Threads](#managing-threads)
|
|
23
|
+
- [Submitting Prompts](#submitting-prompts)
|
|
24
|
+
- [Connection Pooling](#connection-pooling)
|
|
25
|
+
- [Running Examples](#running-examples)
|
|
13
26
|
- [Contributing](#contributing)
|
|
14
27
|
|
|
15
28
|
# Installation
|
|
@@ -34,16 +47,16 @@ API object is easy:
|
|
|
34
47
|
import asyncio
|
|
35
48
|
|
|
36
49
|
from liminal import Client
|
|
37
|
-
from liminal.
|
|
50
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
38
51
|
|
|
39
52
|
|
|
40
53
|
async def main() -> None:
|
|
41
54
|
"""Create the aiohttp session and run the example."""
|
|
42
55
|
# Create an auth provider to authenticate the user:
|
|
43
|
-
|
|
56
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
44
57
|
|
|
45
58
|
# Create the liminal SDK instance:
|
|
46
|
-
liminal = Client(
|
|
59
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
asyncio.run(main())
|
|
@@ -61,13 +74,15 @@ the following auth providers are supported:
|
|
|
61
74
|
|
|
62
75
|
## Microsoft Entra ID
|
|
63
76
|
|
|
64
|
-
|
|
77
|
+
### Device Code Flow
|
|
78
|
+
|
|
79
|
+
This authentication process with Microsoft Entra ID involves an
|
|
65
80
|
[OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
|
|
66
81
|
to start your app, retrieve a device code from the logs produced by this SDK, and
|
|
67
82
|
provide that code to Microsoft via a web browser. Once you complete the login process,
|
|
68
83
|
the SDK will be authenticated for use with your Liminal instance.
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
To authenticate with this flow, you will need an Entra ID client and tenant ID:
|
|
71
86
|
|
|
72
87
|
- Log into your [Azure portal][azure-portal].
|
|
73
88
|
- Navigate to `Microsoft Entra ID`.
|
|
@@ -76,8 +91,6 @@ the SDK will be authenticated for use with your Liminal instance.
|
|
|
76
91
|
- In the `Overview` of the registration, look for the `Application (client) ID` and
|
|
77
92
|
`Directory (tenant) ID` values.
|
|
78
93
|
|
|
79
|
-
### Authenticating Against Entra ID
|
|
80
|
-
|
|
81
94
|
With a client ID and tenant ID, you can create a Liminal client object and authenticate
|
|
82
95
|
it:
|
|
83
96
|
|
|
@@ -85,16 +98,16 @@ it:
|
|
|
85
98
|
import asyncio
|
|
86
99
|
|
|
87
100
|
from liminal import Client
|
|
88
|
-
from liminal.
|
|
101
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
89
102
|
|
|
90
103
|
|
|
91
104
|
async def main() -> None:
|
|
92
105
|
"""Create the aiohttp session and run the example."""
|
|
93
106
|
# Create an auth provider to authenticate the user:
|
|
94
|
-
|
|
107
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
95
108
|
|
|
96
109
|
# Create the liminal SDK instance and authenticate it:
|
|
97
|
-
liminal = Client(
|
|
110
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
98
111
|
await liminal.authenticate_from_auth_provider()
|
|
99
112
|
|
|
100
113
|
|
|
@@ -136,13 +149,13 @@ callback at any time.
|
|
|
136
149
|
import asyncio
|
|
137
150
|
|
|
138
151
|
from liminal import Client
|
|
139
|
-
from liminal.
|
|
152
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
140
153
|
|
|
141
154
|
|
|
142
155
|
async def main() -> None:
|
|
143
156
|
"""Create the aiohttp session and run the example."""
|
|
144
157
|
# Create an auth provider to authenticate the user:
|
|
145
|
-
|
|
158
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
146
159
|
|
|
147
160
|
# Create the liminal SDK instance and authenticate it:
|
|
148
161
|
liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
@@ -164,7 +177,32 @@ async def main() -> None:
|
|
|
164
177
|
asyncio.run(main())
|
|
165
178
|
```
|
|
166
179
|
|
|
167
|
-
|
|
180
|
+
## Creating a Liminal Client from a Stored Refresh Token
|
|
181
|
+
|
|
182
|
+
Assuming you have a stored refresh token (collected from the callback process shown
|
|
183
|
+
above), it is simple to create a new Limina client using that token:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
import asyncio
|
|
187
|
+
|
|
188
|
+
from liminal import Client
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
async def main() -> None:
|
|
192
|
+
"""Create the aiohttp session and run the example."""
|
|
193
|
+
# Retrieve your stored refresh_token:
|
|
194
|
+
refresh_token = "12345"
|
|
195
|
+
|
|
196
|
+
# Create the client:
|
|
197
|
+
liminal = await client.authenticate_from_refresh_token(refresh_token=refresh_token)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
asyncio.run(main())
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
# Endpoints
|
|
204
|
+
|
|
205
|
+
## Getting Model Instances
|
|
168
206
|
|
|
169
207
|
Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
|
|
170
208
|
instance." The SDK provides several methods to interact with model instances:
|
|
@@ -173,7 +211,7 @@ instance." The SDK provides several methods to interact with model instances:
|
|
|
173
211
|
import asyncio
|
|
174
212
|
|
|
175
213
|
from liminal import Client
|
|
176
|
-
from liminal.
|
|
214
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
177
215
|
|
|
178
216
|
|
|
179
217
|
async def main() -> None:
|
|
@@ -191,7 +229,7 @@ async def main() -> None:
|
|
|
191
229
|
asyncio.run(main())
|
|
192
230
|
```
|
|
193
231
|
|
|
194
|
-
|
|
232
|
+
## Managing Threads
|
|
195
233
|
|
|
196
234
|
Threads are conversations with an LLM instance:
|
|
197
235
|
|
|
@@ -199,7 +237,7 @@ Threads are conversations with an LLM instance:
|
|
|
199
237
|
import asyncio
|
|
200
238
|
|
|
201
239
|
from liminal import Client
|
|
202
|
-
from liminal.
|
|
240
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
203
241
|
|
|
204
242
|
|
|
205
243
|
async def main() -> None:
|
|
@@ -228,7 +266,7 @@ async def main() -> None:
|
|
|
228
266
|
asyncio.run(main())
|
|
229
267
|
```
|
|
230
268
|
|
|
231
|
-
|
|
269
|
+
## Submitting Prompts
|
|
232
270
|
|
|
233
271
|
Submitting prompts is easy:
|
|
234
272
|
|
|
@@ -236,7 +274,7 @@ Submitting prompts is easy:
|
|
|
236
274
|
import asyncio
|
|
237
275
|
|
|
238
276
|
from liminal import Client
|
|
239
|
-
from liminal.
|
|
277
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
240
278
|
|
|
241
279
|
|
|
242
280
|
async def main() -> None:
|
|
@@ -275,6 +313,40 @@ async def main() -> None:
|
|
|
275
313
|
asyncio.run(main())
|
|
276
314
|
```
|
|
277
315
|
|
|
316
|
+
# Connection Pooling
|
|
317
|
+
|
|
318
|
+
By default, the library creates a new connection to the Liminal API server with each
|
|
319
|
+
coroutine. If you are calling a large number of coroutines (or merely want to squeeze
|
|
320
|
+
out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
|
|
321
|
+
used for connection pooling:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
import asyncio
|
|
325
|
+
|
|
326
|
+
from liminal import Client
|
|
327
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
async def main() -> None:
|
|
331
|
+
# Create an auth provider to authenticate the user:
|
|
332
|
+
microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
333
|
+
|
|
334
|
+
# Create the liminal SDK instance with a shared HTTPX AsyncClient:
|
|
335
|
+
async with httpx.AsyncClient() as client:
|
|
336
|
+
liminal = Client(
|
|
337
|
+
microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Get to work!
|
|
341
|
+
# ...
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
asyncio.run(main())
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Check out the examples, the tests, and the source files themselves for method
|
|
348
|
+
signatures and more examples.
|
|
349
|
+
|
|
278
350
|
# Running Examples
|
|
279
351
|
|
|
280
352
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -297,7 +369,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
297
369
|
2. [Fork the repository][fork].
|
|
298
370
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
299
371
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
300
|
-
5. Install the dev environment:
|
|
372
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
301
373
|
6. Code your new feature or bug fix on a new branch.
|
|
302
374
|
7. Write tests that cover your new functionality.
|
|
303
375
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -305,10 +377,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
305
377
|
10. Submit a pull request!
|
|
306
378
|
|
|
307
379
|
[azure-portal]: https://portal.azure.com
|
|
380
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
|
|
308
381
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
309
382
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
310
383
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
311
384
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
385
|
+
[httpx]: https://www.python-httpx.org/
|
|
312
386
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
313
387
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
314
388
|
[license]: https://github.com/liminal-ai-security/liminal-sdk-python/blob/main/LICENSE
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Define auth providers."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthProvider:
|
|
5
|
+
"""Define an auth provider abstract base class."""
|
|
6
|
+
|
|
7
|
+
async def get_access_token(self) -> str:
|
|
8
|
+
"""Retrieve an access token from the auth provider.
|
|
9
|
+
|
|
10
|
+
The working principle here is that the auth provider will return an access
|
|
11
|
+
token that can be used to authenticate with the Liminal API server.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
The access token.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
raise NotImplementedError
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Define Microsoft providers."""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Define
|
|
1
|
+
"""Define Microsoft providers."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,18 +7,18 @@ from typing import Final
|
|
|
7
7
|
|
|
8
8
|
from msal import PublicClientApplication
|
|
9
9
|
|
|
10
|
-
from liminal.
|
|
11
|
-
from liminal.
|
|
12
|
-
AuthProvider,
|
|
10
|
+
from liminal.auth import AuthProvider
|
|
11
|
+
from liminal.auth.microsoft.models import (
|
|
13
12
|
MSALCacheTokenResponse,
|
|
14
13
|
MSALIdentityProviderTokenResponse,
|
|
15
14
|
)
|
|
15
|
+
from liminal.const import LOGGER
|
|
16
16
|
from liminal.errors import AuthError
|
|
17
17
|
|
|
18
18
|
DEFAULT_AUTH_CHALLENGE_TIMEOUT: Final[int] = 60
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class DeviceCodeFlowProvider(AuthProvider):
|
|
22
22
|
"""Define a Microsoft auth provider."""
|
|
23
23
|
|
|
24
24
|
AUTHORITY_URL: Final[str] = "https://login.microsoftonline.com"
|
|
@@ -5,27 +5,11 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Literal
|
|
7
7
|
|
|
8
|
-
from liminal.helpers.model import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class AuthProvider:
|
|
12
|
-
"""Define an auth provider abstract base class."""
|
|
13
|
-
|
|
14
|
-
async def get_access_token(self) -> str:
|
|
15
|
-
"""Retrieve an access token from the auth provider.
|
|
16
|
-
|
|
17
|
-
The working principle here is that the auth provider will return an access
|
|
18
|
-
token that can be used to authenticate with the Liminal API server.
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
The access token.
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
raise NotImplementedError
|
|
8
|
+
from liminal.helpers.model import BaseResponseModel
|
|
25
9
|
|
|
26
10
|
|
|
27
11
|
@dataclass(frozen=True, kw_only=True)
|
|
28
|
-
class MSALCacheTokenResponse(
|
|
12
|
+
class MSALCacheTokenResponse(BaseResponseModel):
|
|
29
13
|
"""Define an MSAL token response from Entra ID."""
|
|
30
14
|
|
|
31
15
|
token_type: Literal["Bearer"]
|
|
@@ -35,7 +19,7 @@ class MSALCacheTokenResponse(BaseModel):
|
|
|
35
19
|
|
|
36
20
|
|
|
37
21
|
@dataclass(frozen=True, kw_only=True)
|
|
38
|
-
class MSALIdentityProviderTokenResponse(
|
|
22
|
+
class MSALIdentityProviderTokenResponse(BaseResponseModel):
|
|
39
23
|
"""Define an MSAL token response from the local in-memory cache."""
|
|
40
24
|
|
|
41
25
|
token_type: Literal["Bearer"]
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
|
+
from json.decoder import JSONDecodeError
|
|
8
9
|
from typing import Final
|
|
9
10
|
|
|
10
11
|
from httpx import AsyncClient, Cookies, HTTPStatusError, Request, Response
|
|
@@ -15,8 +16,8 @@ from mashumaro.exceptions import (
|
|
|
15
16
|
UnserializableDataError,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
from liminal.auth import AuthProvider
|
|
18
20
|
from liminal.const import LOGGER
|
|
19
|
-
from liminal.endpoints.auth import AuthProvider
|
|
20
21
|
from liminal.endpoints.llm import LLMEndpoint
|
|
21
22
|
from liminal.endpoints.prompt import PromptEndpoint
|
|
22
23
|
from liminal.endpoints.thread import ThreadEndpoint
|
|
@@ -55,7 +56,6 @@ class Client:
|
|
|
55
56
|
# Token information:
|
|
56
57
|
self._access_token: str | None = None
|
|
57
58
|
self._access_token_expires_at: datetime | None = None
|
|
58
|
-
self._refresh_event = asyncio.Event()
|
|
59
59
|
self._refresh_lock = asyncio.Lock()
|
|
60
60
|
self._refresh_token: str | None = None
|
|
61
61
|
self._refresh_token_callbacks: list[Callable[[str], None]] = []
|
|
@@ -75,7 +75,6 @@ class Client:
|
|
|
75
75
|
cookies: Cookies | None = None,
|
|
76
76
|
params: dict[str, str] | None = None,
|
|
77
77
|
json: dict[str, str] | None = None,
|
|
78
|
-
refresh_request: bool = False,
|
|
79
78
|
) -> Response:
|
|
80
79
|
"""Make a request to the Liminal API server and return a Response.
|
|
81
80
|
|
|
@@ -86,7 +85,6 @@ class Client:
|
|
|
86
85
|
cookies: The cookies to use.
|
|
87
86
|
params: The query parameters to use.
|
|
88
87
|
json: The JSON body to use.
|
|
89
|
-
refresh_request: Whether or not this request is a refresh request.
|
|
90
88
|
|
|
91
89
|
Returns:
|
|
92
90
|
An HTTPX Response object.
|
|
@@ -102,14 +100,6 @@ class Client:
|
|
|
102
100
|
self._access_token_expires_at = None
|
|
103
101
|
await self.authenticate_from_refresh_token()
|
|
104
102
|
|
|
105
|
-
# If an authenticated request arrives while we're refreshing, hold until the
|
|
106
|
-
# refresh process is done:
|
|
107
|
-
if not refresh_request and self._refreshing:
|
|
108
|
-
await self._refresh_event.wait()
|
|
109
|
-
|
|
110
|
-
if not endpoint.startswith("/"):
|
|
111
|
-
endpoint = f"/{endpoint}"
|
|
112
|
-
|
|
113
103
|
url = f"{self._api_server_url}{endpoint}"
|
|
114
104
|
|
|
115
105
|
if not headers:
|
|
@@ -182,6 +172,7 @@ class Client:
|
|
|
182
172
|
try:
|
|
183
173
|
return json_decode(response.content, expected_response_type)
|
|
184
174
|
except (
|
|
175
|
+
JSONDecodeError,
|
|
185
176
|
MissingField,
|
|
186
177
|
SuitableVariantNotFoundError,
|
|
187
178
|
UnserializableDataError,
|
|
@@ -260,19 +251,15 @@ class Client:
|
|
|
260
251
|
msg = "No valid refresh token provided"
|
|
261
252
|
raise AuthError(msg)
|
|
262
253
|
|
|
263
|
-
self._refreshing = True
|
|
264
|
-
|
|
265
254
|
async with self._refresh_lock:
|
|
266
|
-
self.
|
|
255
|
+
self._refreshing = True
|
|
267
256
|
|
|
268
257
|
try:
|
|
269
258
|
refresh_token_response = await self._request(
|
|
270
259
|
"POST",
|
|
271
260
|
"/api/v1/auth/refresh-token",
|
|
272
|
-
refresh_request=True,
|
|
273
261
|
cookies=Cookies({"refreshToken": refresh_token}),
|
|
274
262
|
)
|
|
275
263
|
self._save_tokens_from_auth_response(refresh_token_response)
|
|
276
264
|
finally:
|
|
277
265
|
self._refreshing = False
|
|
278
|
-
self._refresh_event.set()
|