liminal-sdk-python 2024.3.4__tar.gz → 2024.4.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.
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/PKG-INFO +113 -32
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/README.md +111 -30
- liminal_sdk_python-2024.4.0/liminal/auth/__init__.py +18 -0
- liminal_sdk_python-2024.4.0/liminal/auth/microsoft/__init__.py +1 -0
- liminal_sdk_python-2024.3.4/liminal/endpoints/auth/__init__.py → liminal_sdk_python-2024.4.0/liminal/auth/microsoft/device_code_flow.py +11 -8
- {liminal_sdk_python-2024.3.4/liminal/endpoints/auth → liminal_sdk_python-2024.4.0/liminal/auth/microsoft}/models.py +3 -19
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/client.py +16 -25
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/const.py +2 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/endpoints/llm/__init__.py +6 -1
- liminal_sdk_python-2024.4.0/liminal/endpoints/llm/models.py +79 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/endpoints/prompt/__init__.py +66 -21
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/endpoints/prompt/models.py +28 -28
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/endpoints/thread/__init__.py +16 -26
- liminal_sdk_python-2024.4.0/liminal/endpoints/thread/models.py +61 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/helpers/model.py +2 -2
- liminal_sdk_python-2024.4.0/pyproject.toml +396 -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.4.0}/LICENSE +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/endpoints/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/errors.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/liminal/helpers/__init__.py +0 -0
- {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.4.0}/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
|
+
Version: 2024.4.0
|
|
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
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
17
17
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
18
18
|
Requires-Dist: httpx (==0.27.0)
|
|
19
19
|
Requires-Dist: mashumaro (==3.12)
|
|
20
|
-
Requires-Dist: msal (==1.
|
|
20
|
+
Requires-Dist: msal (==1.28.0)
|
|
21
21
|
Project-URL: Bug Tracker, https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
22
22
|
Project-URL: Changelog, https://github.com/liminal-ai-security/liminal-sdk-python/releases
|
|
23
23
|
Project-URL: Repository, https://github.com/liminal-ai-security/liminal-sdk-python
|
|
@@ -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:
|
|
@@ -245,15 +283,11 @@ async def main() -> None:
|
|
|
245
283
|
thread = await liminal.thread.create(model_instance.id, "New Thread")
|
|
246
284
|
# >>> Thread(...)
|
|
247
285
|
|
|
248
|
-
# Get the "de-identified" (containing sensitive data) context history for a thread:
|
|
249
|
-
thread = await liminal.thread.get_deidentified_context_history(model_instance.id)
|
|
250
|
-
# >>> [DeidentifiedToken(...), DeidentifiedToken(...), DeidentifiedToken(...)]
|
|
251
|
-
|
|
252
286
|
|
|
253
287
|
asyncio.run(main())
|
|
254
288
|
```
|
|
255
289
|
|
|
256
|
-
|
|
290
|
+
## Submitting Prompts
|
|
257
291
|
|
|
258
292
|
Submitting prompts is easy:
|
|
259
293
|
|
|
@@ -261,38 +295,49 @@ Submitting prompts is easy:
|
|
|
261
295
|
import asyncio
|
|
262
296
|
|
|
263
297
|
from liminal import Client
|
|
264
|
-
from liminal.
|
|
298
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
265
299
|
|
|
266
300
|
|
|
267
301
|
async def main() -> None:
|
|
268
302
|
# Assuming you have an authenticated `liminal` object:
|
|
269
303
|
|
|
270
|
-
# Prompt operations require a
|
|
304
|
+
# Prompt operations require a model instance:
|
|
305
|
+
model_instance = await liminal.llm.get_model_instance(model_instance_name)
|
|
306
|
+
|
|
307
|
+
# Prompt operations optionally take an existing thread:
|
|
271
308
|
thread = await liminal.thread.get_by_id(123)
|
|
272
309
|
# >>> Thread(...)
|
|
273
310
|
|
|
274
311
|
# Analayze a prompt for sensitive info:
|
|
275
|
-
findings = await liminal.prompt.analyze(
|
|
312
|
+
findings = await liminal.prompt.analyze(
|
|
313
|
+
model_instance.id, "Here is a sensitive prompt"
|
|
314
|
+
)
|
|
276
315
|
# >>> AnalyzeResponse(...)
|
|
277
316
|
|
|
278
317
|
# Cleanse input text by applying the policies defined in the Liminal admin
|
|
279
318
|
# dashboard. You can optionally provide existing analysis finidings; if not
|
|
280
319
|
# provided, analyze is # called automatically):
|
|
281
320
|
cleansed = await liminal.prompt.cleanse(
|
|
282
|
-
|
|
321
|
+
model_instance.id,
|
|
322
|
+
"Here is a sensitive prompt",
|
|
323
|
+
findings=findings,
|
|
324
|
+
thread_id=thread.id,
|
|
283
325
|
)
|
|
284
326
|
# >>> CleanseResponse(...)
|
|
285
327
|
|
|
286
328
|
# Submit a prompt to an LLM, cleansing it in the process (once again, providing optional
|
|
287
329
|
# findings):
|
|
288
330
|
response = await liminal.prompt.submit(
|
|
289
|
-
|
|
331
|
+
model_instance.id,
|
|
332
|
+
"Here is a sensitive prompt",
|
|
333
|
+
findings=findings,
|
|
334
|
+
thread_id=thread.id,
|
|
290
335
|
)
|
|
291
|
-
# >>>
|
|
336
|
+
# >>> SubmitResponse(...)
|
|
292
337
|
|
|
293
338
|
# Rehydrate a response with sensitive data:
|
|
294
339
|
hydrated = await liminal.prompt.hydrate(
|
|
295
|
-
|
|
340
|
+
model_instance.id, "Here is a response to rehdyrate", thread_id=thread.id
|
|
296
341
|
)
|
|
297
342
|
# >>> HydrateResponse(...)
|
|
298
343
|
|
|
@@ -300,6 +345,40 @@ async def main() -> None:
|
|
|
300
345
|
asyncio.run(main())
|
|
301
346
|
```
|
|
302
347
|
|
|
348
|
+
# Connection Pooling
|
|
349
|
+
|
|
350
|
+
By default, the library creates a new connection to the Liminal API server with each
|
|
351
|
+
coroutine. If you are calling a large number of coroutines (or merely want to squeeze
|
|
352
|
+
out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
|
|
353
|
+
used for connection pooling:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
import asyncio
|
|
357
|
+
|
|
358
|
+
from liminal import Client
|
|
359
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def main() -> None:
|
|
363
|
+
# Create an auth provider to authenticate the user:
|
|
364
|
+
microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
365
|
+
|
|
366
|
+
# Create the liminal SDK instance with a shared HTTPX AsyncClient:
|
|
367
|
+
async with httpx.AsyncClient() as client:
|
|
368
|
+
liminal = Client(
|
|
369
|
+
microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Get to work!
|
|
373
|
+
# ...
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
asyncio.run(main())
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Check out the examples, the tests, and the source files themselves for method
|
|
380
|
+
signatures and more examples.
|
|
381
|
+
|
|
303
382
|
# Running Examples
|
|
304
383
|
|
|
305
384
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -322,7 +401,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
322
401
|
2. [Fork the repository][fork].
|
|
323
402
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
324
403
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
325
|
-
5. Install the dev environment:
|
|
404
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
326
405
|
6. Code your new feature or bug fix on a new branch.
|
|
327
406
|
7. Write tests that cover your new functionality.
|
|
328
407
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -330,10 +409,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
330
409
|
10. Submit a pull request!
|
|
331
410
|
|
|
332
411
|
[azure-portal]: https://portal.azure.com
|
|
412
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
|
|
333
413
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
334
414
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
335
415
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
336
416
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
417
|
+
[httpx]: https://www.python-httpx.org/
|
|
337
418
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
338
419
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
339
420
|
[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:
|
|
@@ -220,15 +258,11 @@ async def main() -> None:
|
|
|
220
258
|
thread = await liminal.thread.create(model_instance.id, "New Thread")
|
|
221
259
|
# >>> Thread(...)
|
|
222
260
|
|
|
223
|
-
# Get the "de-identified" (containing sensitive data) context history for a thread:
|
|
224
|
-
thread = await liminal.thread.get_deidentified_context_history(model_instance.id)
|
|
225
|
-
# >>> [DeidentifiedToken(...), DeidentifiedToken(...), DeidentifiedToken(...)]
|
|
226
|
-
|
|
227
261
|
|
|
228
262
|
asyncio.run(main())
|
|
229
263
|
```
|
|
230
264
|
|
|
231
|
-
|
|
265
|
+
## Submitting Prompts
|
|
232
266
|
|
|
233
267
|
Submitting prompts is easy:
|
|
234
268
|
|
|
@@ -236,38 +270,49 @@ Submitting prompts is easy:
|
|
|
236
270
|
import asyncio
|
|
237
271
|
|
|
238
272
|
from liminal import Client
|
|
239
|
-
from liminal.
|
|
273
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
240
274
|
|
|
241
275
|
|
|
242
276
|
async def main() -> None:
|
|
243
277
|
# Assuming you have an authenticated `liminal` object:
|
|
244
278
|
|
|
245
|
-
# Prompt operations require a
|
|
279
|
+
# Prompt operations require a model instance:
|
|
280
|
+
model_instance = await liminal.llm.get_model_instance(model_instance_name)
|
|
281
|
+
|
|
282
|
+
# Prompt operations optionally take an existing thread:
|
|
246
283
|
thread = await liminal.thread.get_by_id(123)
|
|
247
284
|
# >>> Thread(...)
|
|
248
285
|
|
|
249
286
|
# Analayze a prompt for sensitive info:
|
|
250
|
-
findings = await liminal.prompt.analyze(
|
|
287
|
+
findings = await liminal.prompt.analyze(
|
|
288
|
+
model_instance.id, "Here is a sensitive prompt"
|
|
289
|
+
)
|
|
251
290
|
# >>> AnalyzeResponse(...)
|
|
252
291
|
|
|
253
292
|
# Cleanse input text by applying the policies defined in the Liminal admin
|
|
254
293
|
# dashboard. You can optionally provide existing analysis finidings; if not
|
|
255
294
|
# provided, analyze is # called automatically):
|
|
256
295
|
cleansed = await liminal.prompt.cleanse(
|
|
257
|
-
|
|
296
|
+
model_instance.id,
|
|
297
|
+
"Here is a sensitive prompt",
|
|
298
|
+
findings=findings,
|
|
299
|
+
thread_id=thread.id,
|
|
258
300
|
)
|
|
259
301
|
# >>> CleanseResponse(...)
|
|
260
302
|
|
|
261
303
|
# Submit a prompt to an LLM, cleansing it in the process (once again, providing optional
|
|
262
304
|
# findings):
|
|
263
305
|
response = await liminal.prompt.submit(
|
|
264
|
-
|
|
306
|
+
model_instance.id,
|
|
307
|
+
"Here is a sensitive prompt",
|
|
308
|
+
findings=findings,
|
|
309
|
+
thread_id=thread.id,
|
|
265
310
|
)
|
|
266
|
-
# >>>
|
|
311
|
+
# >>> SubmitResponse(...)
|
|
267
312
|
|
|
268
313
|
# Rehydrate a response with sensitive data:
|
|
269
314
|
hydrated = await liminal.prompt.hydrate(
|
|
270
|
-
|
|
315
|
+
model_instance.id, "Here is a response to rehdyrate", thread_id=thread.id
|
|
271
316
|
)
|
|
272
317
|
# >>> HydrateResponse(...)
|
|
273
318
|
|
|
@@ -275,6 +320,40 @@ async def main() -> None:
|
|
|
275
320
|
asyncio.run(main())
|
|
276
321
|
```
|
|
277
322
|
|
|
323
|
+
# Connection Pooling
|
|
324
|
+
|
|
325
|
+
By default, the library creates a new connection to the Liminal API server with each
|
|
326
|
+
coroutine. If you are calling a large number of coroutines (or merely want to squeeze
|
|
327
|
+
out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
|
|
328
|
+
used for connection pooling:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
import asyncio
|
|
332
|
+
|
|
333
|
+
from liminal import Client
|
|
334
|
+
from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
async def main() -> None:
|
|
338
|
+
# Create an auth provider to authenticate the user:
|
|
339
|
+
microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
|
|
340
|
+
|
|
341
|
+
# Create the liminal SDK instance with a shared HTTPX AsyncClient:
|
|
342
|
+
async with httpx.AsyncClient() as client:
|
|
343
|
+
liminal = Client(
|
|
344
|
+
microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Get to work!
|
|
348
|
+
# ...
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
asyncio.run(main())
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Check out the examples, the tests, and the source files themselves for method
|
|
355
|
+
signatures and more examples.
|
|
356
|
+
|
|
278
357
|
# Running Examples
|
|
279
358
|
|
|
280
359
|
You can see examples of how to use this SDK via the [`examples`][examples] folder in
|
|
@@ -297,7 +376,7 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
297
376
|
2. [Fork the repository][fork].
|
|
298
377
|
3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
|
|
299
378
|
4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
|
|
300
|
-
5. Install the dev environment:
|
|
379
|
+
5. Install the dev environment: `./scripts/setup.sh`
|
|
301
380
|
6. Code your new feature or bug fix on a new branch.
|
|
302
381
|
7. Write tests that cover your new functionality.
|
|
303
382
|
8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
|
|
@@ -305,10 +384,12 @@ Thanks to all of [our contributors][contributors] so far!
|
|
|
305
384
|
10. Submit a pull request!
|
|
306
385
|
|
|
307
386
|
[azure-portal]: https://portal.azure.com
|
|
387
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
|
|
308
388
|
[ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
|
|
309
389
|
[contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
|
|
310
390
|
[examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
|
|
311
391
|
[fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
|
|
392
|
+
[httpx]: https://www.python-httpx.org/
|
|
312
393
|
[issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
|
|
313
394
|
[license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
|
|
314
395
|
[license]: https://github.com/liminal-ai-security/liminal-sdk-python/blob/main/LICENSE
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Define auth providers."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthProvider: # pylint: disable=too-few-public-methods
|
|
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
|
+
-------
|
|
15
|
+
The access token.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
raise NotImplementedError
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Define Microsoft providers."""
|