liminal-sdk-python 2024.3.3__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.3 → liminal_sdk_python-2024.3.5}/PKG-INFO +95 -27
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/README.md +94 -26
- 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.3/liminal/endpoints/auth/__init__.py → liminal_sdk_python-2024.3.5/liminal/auth/microsoft/device_code_flow.py +13 -9
- {liminal_sdk_python-2024.3.3/liminal/endpoints/auth → liminal_sdk_python-2024.3.5/liminal/auth/microsoft}/models.py +3 -20
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/client.py +20 -29
- liminal_sdk_python-2024.3.5/liminal/const.py +6 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/llm/__init__.py +6 -6
- liminal_sdk_python-2024.3.5/liminal/endpoints/llm/models.py +79 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/__init__.py +2 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/models.py +28 -28
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/thread/__init__.py +2 -0
- liminal_sdk_python-2024.3.5/liminal/endpoints/thread/models.py +61 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/helpers/model.py +2 -2
- liminal_sdk_python-2024.3.5/pyproject.toml +115 -0
- liminal_sdk_python-2024.3.3/liminal/const.py +0 -5
- liminal_sdk_python-2024.3.3/liminal/endpoints/llm/models.py +0 -46
- liminal_sdk_python-2024.3.3/liminal/endpoints/thread/models.py +0 -31
- liminal_sdk_python-2024.3.3/pyproject.toml +0 -119
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/LICENSE +0 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/__init__.py +0 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/__init__.py +0 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/errors.py +0 -0
- {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/helpers/__init__.py +0 -0
- {liminal_sdk_python-2024.3.3 → 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,18 +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
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
68
|
-
|
|
69
80
|
# Create an auth provider to authenticate the user:
|
|
70
|
-
|
|
81
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
71
82
|
|
|
72
83
|
# Create the liminal SDK instance:
|
|
73
|
-
liminal = Client(
|
|
84
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
74
85
|
|
|
75
86
|
|
|
76
87
|
asyncio.run(main())
|
|
@@ -88,13 +99,15 @@ the following auth providers are supported:
|
|
|
88
99
|
|
|
89
100
|
## Microsoft Entra ID
|
|
90
101
|
|
|
91
|
-
|
|
102
|
+
### Device Code Flow
|
|
103
|
+
|
|
104
|
+
This authentication process with Microsoft Entra ID involves an
|
|
92
105
|
[OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
|
|
93
106
|
to start your app, retrieve a device code from the logs produced by this SDK, and
|
|
94
107
|
provide that code to Microsoft via a web browser. Once you complete the login process,
|
|
95
108
|
the SDK will be authenticated for use with your Liminal instance.
|
|
96
109
|
|
|
97
|
-
|
|
110
|
+
To authenticate with this flow, you will need an Entra ID client and tenant ID:
|
|
98
111
|
|
|
99
112
|
- Log into your [Azure portal][azure-portal].
|
|
100
113
|
- Navigate to `Microsoft Entra ID`.
|
|
@@ -103,8 +116,6 @@ the SDK will be authenticated for use with your Liminal instance.
|
|
|
103
116
|
- In the `Overview` of the registration, look for the `Application (client) ID` and
|
|
104
117
|
`Directory (tenant) ID` values.
|
|
105
118
|
|
|
106
|
-
### Authenticating Against Entra ID
|
|
107
|
-
|
|
108
119
|
With a client ID and tenant ID, you can create a Liminal client object and authenticate
|
|
109
120
|
it:
|
|
110
121
|
|
|
@@ -112,18 +123,16 @@ it:
|
|
|
112
123
|
import asyncio
|
|
113
124
|
|
|
114
125
|
from liminal import Client
|
|
115
|
-
from liminal.
|
|
126
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
116
127
|
|
|
117
128
|
|
|
118
129
|
async def main() -> None:
|
|
119
130
|
"""Create the aiohttp session and run the example."""
|
|
120
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
121
|
-
|
|
122
131
|
# Create an auth provider to authenticate the user:
|
|
123
|
-
|
|
132
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
124
133
|
|
|
125
134
|
# Create the liminal SDK instance and authenticate it:
|
|
126
|
-
liminal = Client(
|
|
135
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
127
136
|
await liminal.authenticate_from_auth_provider()
|
|
128
137
|
|
|
129
138
|
|
|
@@ -165,15 +174,13 @@ callback at any time.
|
|
|
165
174
|
import asyncio
|
|
166
175
|
|
|
167
176
|
from liminal import Client
|
|
168
|
-
from liminal.
|
|
177
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
169
178
|
|
|
170
179
|
|
|
171
180
|
async def main() -> None:
|
|
172
181
|
"""Create the aiohttp session and run the example."""
|
|
173
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
174
|
-
|
|
175
182
|
# Create an auth provider to authenticate the user:
|
|
176
|
-
|
|
183
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
177
184
|
|
|
178
185
|
# Create the liminal SDK instance and authenticate it:
|
|
179
186
|
liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
@@ -195,7 +202,32 @@ async def main() -> None:
|
|
|
195
202
|
asyncio.run(main())
|
|
196
203
|
```
|
|
197
204
|
|
|
198
|
-
|
|
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
|
|
199
231
|
|
|
200
232
|
Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
|
|
201
233
|
instance." The SDK provides several methods to interact with model instances:
|
|
@@ -204,7 +236,7 @@ instance." The SDK provides several methods to interact with model instances:
|
|
|
204
236
|
import asyncio
|
|
205
237
|
|
|
206
238
|
from liminal import Client
|
|
207
|
-
from liminal.
|
|
239
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
208
240
|
|
|
209
241
|
|
|
210
242
|
async def main() -> None:
|
|
@@ -222,7 +254,7 @@ async def main() -> None:
|
|
|
222
254
|
asyncio.run(main())
|
|
223
255
|
```
|
|
224
256
|
|
|
225
|
-
|
|
257
|
+
## Managing Threads
|
|
226
258
|
|
|
227
259
|
Threads are conversations with an LLM instance:
|
|
228
260
|
|
|
@@ -230,7 +262,7 @@ Threads are conversations with an LLM instance:
|
|
|
230
262
|
import asyncio
|
|
231
263
|
|
|
232
264
|
from liminal import Client
|
|
233
|
-
from liminal.
|
|
265
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
234
266
|
|
|
235
267
|
|
|
236
268
|
async def main() -> None:
|
|
@@ -259,7 +291,7 @@ async def main() -> None:
|
|
|
259
291
|
asyncio.run(main())
|
|
260
292
|
```
|
|
261
293
|
|
|
262
|
-
|
|
294
|
+
## Submitting Prompts
|
|
263
295
|
|
|
264
296
|
Submitting prompts is easy:
|
|
265
297
|
|
|
@@ -267,7 +299,7 @@ Submitting prompts is easy:
|
|
|
267
299
|
import asyncio
|
|
268
300
|
|
|
269
301
|
from liminal import Client
|
|
270
|
-
from liminal.
|
|
302
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
271
303
|
|
|
272
304
|
|
|
273
305
|
async def main() -> None:
|
|
@@ -306,6 +338,40 @@ async def main() -> None:
|
|
|
306
338
|
asyncio.run(main())
|
|
307
339
|
```
|
|
308
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
|
+
|
|
309
375
|
# Running Examples
|
|
310
376
|
|
|
311
377
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -328,7 +394,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
328
394
|
2. [Fork the repository][fork].
|
|
329
395
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
330
396
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
331
|
-
5. Install the dev environment:
|
|
397
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
332
398
|
6. Code your new feature or bug fix on a new branch.
|
|
333
399
|
7. Write tests that cover your new functionality.
|
|
334
400
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -336,10 +402,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
336
402
|
10. Submit a pull request!
|
|
337
403
|
|
|
338
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
|
|
339
406
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
340
407
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
341
408
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
342
409
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
410
|
+
[httpx]: https://www.python-httpx.org/
|
|
343
411
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
344
412
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
345
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,18 +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
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
43
|
-
|
|
44
55
|
# Create an auth provider to authenticate the user:
|
|
45
|
-
|
|
56
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
46
57
|
|
|
47
58
|
# Create the liminal SDK instance:
|
|
48
|
-
liminal = Client(
|
|
59
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
49
60
|
|
|
50
61
|
|
|
51
62
|
asyncio.run(main())
|
|
@@ -63,13 +74,15 @@ the following auth providers are supported:
|
|
|
63
74
|
|
|
64
75
|
## Microsoft Entra ID
|
|
65
76
|
|
|
66
|
-
|
|
77
|
+
### Device Code Flow
|
|
78
|
+
|
|
79
|
+
This authentication process with Microsoft Entra ID involves an
|
|
67
80
|
[OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
|
|
68
81
|
to start your app, retrieve a device code from the logs produced by this SDK, and
|
|
69
82
|
provide that code to Microsoft via a web browser. Once you complete the login process,
|
|
70
83
|
the SDK will be authenticated for use with your Liminal instance.
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
To authenticate with this flow, you will need an Entra ID client and tenant ID:
|
|
73
86
|
|
|
74
87
|
- Log into your [Azure portal][azure-portal].
|
|
75
88
|
- Navigate to `Microsoft Entra ID`.
|
|
@@ -78,8 +91,6 @@ the SDK will be authenticated for use with your Liminal instance.
|
|
|
78
91
|
- In the `Overview` of the registration, look for the `Application (client) ID` and
|
|
79
92
|
`Directory (tenant) ID` values.
|
|
80
93
|
|
|
81
|
-
### Authenticating Against Entra ID
|
|
82
|
-
|
|
83
94
|
With a client ID and tenant ID, you can create a Liminal client object and authenticate
|
|
84
95
|
it:
|
|
85
96
|
|
|
@@ -87,18 +98,16 @@ it:
|
|
|
87
98
|
import asyncio
|
|
88
99
|
|
|
89
100
|
from liminal import Client
|
|
90
|
-
from liminal.
|
|
101
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
91
102
|
|
|
92
103
|
|
|
93
104
|
async def main() -> None:
|
|
94
105
|
"""Create the aiohttp session and run the example."""
|
|
95
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
96
|
-
|
|
97
106
|
# Create an auth provider to authenticate the user:
|
|
98
|
-
|
|
107
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
99
108
|
|
|
100
109
|
# Create the liminal SDK instance and authenticate it:
|
|
101
|
-
liminal = Client(
|
|
110
|
+
liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
102
111
|
await liminal.authenticate_from_auth_provider()
|
|
103
112
|
|
|
104
113
|
|
|
@@ -140,15 +149,13 @@ callback at any time.
|
|
|
140
149
|
import asyncio
|
|
141
150
|
|
|
142
151
|
from liminal import Client
|
|
143
|
-
from liminal.
|
|
152
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
144
153
|
|
|
145
154
|
|
|
146
155
|
async def main() -> None:
|
|
147
156
|
"""Create the aiohttp session and run the example."""
|
|
148
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
149
|
-
|
|
150
157
|
# Create an auth provider to authenticate the user:
|
|
151
|
-
|
|
158
|
+
auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
152
159
|
|
|
153
160
|
# Create the liminal SDK instance and authenticate it:
|
|
154
161
|
liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
|
|
@@ -170,7 +177,32 @@ async def main() -> None:
|
|
|
170
177
|
asyncio.run(main())
|
|
171
178
|
```
|
|
172
179
|
|
|
173
|
-
|
|
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
|
|
174
206
|
|
|
175
207
|
Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
|
|
176
208
|
instance." The SDK provides several methods to interact with model instances:
|
|
@@ -179,7 +211,7 @@ instance." The SDK provides several methods to interact with model instances:
|
|
|
179
211
|
import asyncio
|
|
180
212
|
|
|
181
213
|
from liminal import Client
|
|
182
|
-
from liminal.
|
|
214
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
183
215
|
|
|
184
216
|
|
|
185
217
|
async def main() -> None:
|
|
@@ -197,7 +229,7 @@ async def main() -> None:
|
|
|
197
229
|
asyncio.run(main())
|
|
198
230
|
```
|
|
199
231
|
|
|
200
|
-
|
|
232
|
+
## Managing Threads
|
|
201
233
|
|
|
202
234
|
Threads are conversations with an LLM instance:
|
|
203
235
|
|
|
@@ -205,7 +237,7 @@ Threads are conversations with an LLM instance:
|
|
|
205
237
|
import asyncio
|
|
206
238
|
|
|
207
239
|
from liminal import Client
|
|
208
|
-
from liminal.
|
|
240
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
209
241
|
|
|
210
242
|
|
|
211
243
|
async def main() -> None:
|
|
@@ -234,7 +266,7 @@ async def main() -> None:
|
|
|
234
266
|
asyncio.run(main())
|
|
235
267
|
```
|
|
236
268
|
|
|
237
|
-
|
|
269
|
+
## Submitting Prompts
|
|
238
270
|
|
|
239
271
|
Submitting prompts is easy:
|
|
240
272
|
|
|
@@ -242,7 +274,7 @@ Submitting prompts is easy:
|
|
|
242
274
|
import asyncio
|
|
243
275
|
|
|
244
276
|
from liminal import Client
|
|
245
|
-
from liminal.
|
|
277
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
246
278
|
|
|
247
279
|
|
|
248
280
|
async def main() -> None:
|
|
@@ -281,6 +313,40 @@ async def main() -> None:
|
|
|
281
313
|
asyncio.run(main())
|
|
282
314
|
```
|
|
283
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
|
+
|
|
284
350
|
# Running Examples
|
|
285
351
|
|
|
286
352
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -303,7 +369,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
303
369
|
2. [Fork the repository][fork].
|
|
304
370
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
305
371
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
306
|
-
5. Install the dev environment:
|
|
372
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
307
373
|
6. Code your new feature or bug fix on a new branch.
|
|
308
374
|
7. Write tests that cover your new functionality.
|
|
309
375
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -311,10 +377,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
311
377
|
10. Submit a pull request!
|
|
312
378
|
|
|
313
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
|
|
314
381
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
315
382
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
316
383
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
317
384
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
385
|
+
[httpx]: https://www.python-httpx.org/
|
|
318
386
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
319
387
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
320
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,25 +1,28 @@
|
|
|
1
|
-
"""Define
|
|
1
|
+
"""Define Microsoft providers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import asyncio
|
|
6
|
+
from typing import Final
|
|
4
7
|
|
|
5
8
|
from msal import PublicClientApplication
|
|
6
9
|
|
|
7
|
-
from liminal.
|
|
8
|
-
from liminal.
|
|
9
|
-
AuthProvider,
|
|
10
|
+
from liminal.auth import AuthProvider
|
|
11
|
+
from liminal.auth.microsoft.models import (
|
|
10
12
|
MSALCacheTokenResponse,
|
|
11
13
|
MSALIdentityProviderTokenResponse,
|
|
12
14
|
)
|
|
15
|
+
from liminal.const import LOGGER
|
|
13
16
|
from liminal.errors import AuthError
|
|
14
17
|
|
|
15
|
-
DEFAULT_AUTH_CHALLENGE_TIMEOUT = 60
|
|
18
|
+
DEFAULT_AUTH_CHALLENGE_TIMEOUT: Final[int] = 60
|
|
16
19
|
|
|
17
20
|
|
|
18
|
-
class
|
|
21
|
+
class DeviceCodeFlowProvider(AuthProvider):
|
|
19
22
|
"""Define a Microsoft auth provider."""
|
|
20
23
|
|
|
21
|
-
AUTHORITY_URL = "https://login.microsoftonline.com"
|
|
22
|
-
DEFAULT_SCOPES = ["User.Read"]
|
|
24
|
+
AUTHORITY_URL: Final[str] = "https://login.microsoftonline.com"
|
|
25
|
+
DEFAULT_SCOPES: Final[list[str]] = ["User.Read"]
|
|
23
26
|
|
|
24
27
|
def __init__(
|
|
25
28
|
self,
|
|
@@ -76,7 +79,8 @@ class MicrosoftAuthProvider(AuthProvider):
|
|
|
76
79
|
# Setting the flow to expire immediately will effectively kill the future
|
|
77
80
|
# that we're awaiting:
|
|
78
81
|
flow["expires_at"] = 0
|
|
79
|
-
|
|
82
|
+
msg = "Timed out waiting for authentication challenge"
|
|
83
|
+
raise AuthError(msg) from err
|
|
80
84
|
|
|
81
85
|
identity_provider_response = MSALIdentityProviderTokenResponse.from_dict(result)
|
|
82
86
|
return identity_provider_response.access_token
|
|
@@ -2,31 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from abc import ABC
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from typing import Literal
|
|
8
7
|
|
|
9
|
-
from liminal.helpers.model import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class AuthProvider(ABC):
|
|
13
|
-
"""Define an auth provider abstract base class."""
|
|
14
|
-
|
|
15
|
-
async def get_access_token(self) -> str:
|
|
16
|
-
"""Retrieve an access token from the auth provider.
|
|
17
|
-
|
|
18
|
-
The working principle here is that the auth provider will return an access
|
|
19
|
-
token that can be used to authenticate with the Liminal API server.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
The access token.
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
raise NotImplementedError
|
|
8
|
+
from liminal.helpers.model import BaseResponseModel
|
|
26
9
|
|
|
27
10
|
|
|
28
11
|
@dataclass(frozen=True, kw_only=True)
|
|
29
|
-
class MSALCacheTokenResponse(
|
|
12
|
+
class MSALCacheTokenResponse(BaseResponseModel):
|
|
30
13
|
"""Define an MSAL token response from Entra ID."""
|
|
31
14
|
|
|
32
15
|
token_type: Literal["Bearer"]
|
|
@@ -36,7 +19,7 @@ class MSALCacheTokenResponse(BaseModel):
|
|
|
36
19
|
|
|
37
20
|
|
|
38
21
|
@dataclass(frozen=True, kw_only=True)
|
|
39
|
-
class MSALIdentityProviderTokenResponse(
|
|
22
|
+
class MSALIdentityProviderTokenResponse(BaseResponseModel):
|
|
40
23
|
"""Define an MSAL token response from the local in-memory cache."""
|
|
41
24
|
|
|
42
25
|
token_type: Literal["Bearer"]
|