pangea-sdk 6.5.0b1__py3-none-any.whl → 6.7.0__py3-none-any.whl
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.
- pangea/__init__.py +3 -11
- pangea/_constants.py +4 -0
- pangea/_typing.py +30 -0
- pangea/asyncio/__init__.py +2 -1
- pangea/asyncio/file_uploader.py +3 -2
- pangea/asyncio/request.py +66 -162
- pangea/asyncio/services/__init__.py +19 -3
- pangea/asyncio/services/ai_guard.py +31 -97
- pangea/asyncio/services/audit.py +1 -301
- pangea/asyncio/services/authn.py +25 -8
- pangea/asyncio/services/base.py +21 -6
- pangea/asyncio/services/file_scan.py +1 -1
- pangea/asyncio/services/intel.py +160 -95
- pangea/asyncio/services/prompt_guard.py +7 -113
- pangea/asyncio/services/redact.py +4 -265
- pangea/config.py +4 -2
- pangea/file_uploader.py +4 -1
- pangea/request.py +91 -166
- pangea/response.py +5 -1
- pangea/services/__init__.py +19 -3
- pangea/services/ai_guard.py +180 -597
- pangea/services/audit/audit.py +3 -301
- pangea/services/audit/models.py +1 -273
- pangea/services/audit/util.py +2 -0
- pangea/services/authn/authn.py +4 -5
- pangea/services/base.py +3 -0
- pangea/services/file_scan.py +3 -2
- pangea/services/intel.py +187 -252
- pangea/services/prompt_guard.py +11 -197
- pangea/services/redact.py +7 -473
- pangea/services/vault/vault.py +3 -0
- {pangea_sdk-6.5.0b1.dist-info → pangea_sdk-6.7.0.dist-info}/METADATA +17 -18
- pangea_sdk-6.7.0.dist-info/RECORD +62 -0
- pangea_sdk-6.7.0.dist-info/WHEEL +4 -0
- pangea/asyncio/services/management.py +0 -576
- pangea/services/management.py +0 -720
- pangea_sdk-6.5.0b1.dist-info/RECORD +0 -62
- pangea_sdk-6.5.0b1.dist-info/WHEEL +0 -4
pangea/__init__.py
CHANGED
@@ -1,15 +1,7 @@
|
|
1
|
-
__version__ = "6.
|
1
|
+
__version__ = "6.7.0"
|
2
2
|
|
3
|
-
from pangea.asyncio.request import PangeaRequestAsync
|
4
3
|
from pangea.config import PangeaConfig
|
5
4
|
from pangea.file_uploader import FileUploader
|
6
|
-
from pangea.
|
7
|
-
from pangea.response import PangeaResponse
|
5
|
+
from pangea.response import PangeaResponse, PangeaResponseResult, TransferMethod
|
8
6
|
|
9
|
-
__all__ = (
|
10
|
-
"FileUploader",
|
11
|
-
"PangeaConfig",
|
12
|
-
"PangeaRequest",
|
13
|
-
"PangeaRequestAsync",
|
14
|
-
"PangeaResponse",
|
15
|
-
)
|
7
|
+
__all__ = ("FileUploader", "PangeaConfig", "PangeaResponse", "PangeaResponseResult", "TransferMethod")
|
pangea/_constants.py
ADDED
pangea/_typing.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Iterator, Sequence
|
4
|
+
|
5
|
+
from typing_extensions import Any, Protocol, SupportsIndex, TypeVar, overload
|
6
|
+
|
7
|
+
T = TypeVar("T")
|
8
|
+
T_co = TypeVar("T_co", covariant=True)
|
9
|
+
|
10
|
+
|
11
|
+
class SequenceNotStr(Protocol[T_co]):
|
12
|
+
"""Sequence-like object that isn't str or bytes."""
|
13
|
+
|
14
|
+
@overload
|
15
|
+
def __getitem__(self, index: SupportsIndex, /) -> T_co: ...
|
16
|
+
|
17
|
+
@overload
|
18
|
+
def __getitem__(self, index: slice, /) -> Sequence[T_co]: ...
|
19
|
+
|
20
|
+
def __contains__(self, value: object, /) -> bool: ...
|
21
|
+
|
22
|
+
def __len__(self) -> int: ...
|
23
|
+
|
24
|
+
def __iter__(self) -> Iterator[T_co]: ...
|
25
|
+
|
26
|
+
def index(self, value: Any, start: int = ..., stop: int = ..., /) -> int: ...
|
27
|
+
|
28
|
+
def count(self, value: Any, /) -> int: ...
|
29
|
+
|
30
|
+
def __reversed__(self) -> Iterator[T_co]: ...
|
pangea/asyncio/__init__.py
CHANGED
pangea/asyncio/file_uploader.py
CHANGED
@@ -6,9 +6,10 @@ from __future__ import annotations
|
|
6
6
|
import io
|
7
7
|
import logging
|
8
8
|
|
9
|
+
from pangea import PangeaConfig, TransferMethod
|
9
10
|
from pangea.asyncio.request import PangeaRequestAsync
|
10
|
-
|
11
|
-
|
11
|
+
|
12
|
+
__all__ = ("FileUploaderAsync",)
|
12
13
|
|
13
14
|
|
14
15
|
class FileUploaderAsync:
|
pangea/asyncio/request.py
CHANGED
@@ -10,19 +10,23 @@ import asyncio
|
|
10
10
|
import json
|
11
11
|
import time
|
12
12
|
from collections.abc import Iterable, Mapping
|
13
|
-
from
|
13
|
+
from random import random
|
14
|
+
from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
|
14
15
|
|
15
16
|
import aiohttp
|
16
17
|
from aiohttp import FormData
|
17
|
-
from pydantic import BaseModel
|
18
|
+
from pydantic import BaseModel
|
18
19
|
from pydantic_core import to_jsonable_python
|
19
|
-
from typing_extensions import Any,
|
20
|
+
from typing_extensions import Any, TypeAlias, TypeVar, override
|
20
21
|
|
21
22
|
import pangea.exceptions as pe
|
23
|
+
from pangea._constants import MAX_RETRY_DELAY, RETRYABLE_HTTP_CODES
|
22
24
|
from pangea.request import MultipartResponse, PangeaRequestBase
|
23
25
|
from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
|
24
26
|
from pangea.utils import default_encoder
|
25
27
|
|
28
|
+
__all__ = ("PangeaRequestAsync",)
|
29
|
+
|
26
30
|
_FileName: TypeAlias = Union[str, None]
|
27
31
|
_FileContent: TypeAlias = Union[str, bytes]
|
28
32
|
_FileContentType: TypeAlias = str
|
@@ -50,24 +54,6 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
50
54
|
be set in PangeaConfig.
|
51
55
|
"""
|
52
56
|
|
53
|
-
async def delete(self, endpoint: str) -> None:
|
54
|
-
"""
|
55
|
-
Makes a DELETE call to a Pangea endpoint.
|
56
|
-
|
57
|
-
Args:
|
58
|
-
endpoint: The Pangea API endpoint.
|
59
|
-
"""
|
60
|
-
|
61
|
-
url = self._url(endpoint)
|
62
|
-
|
63
|
-
self.logger.debug(
|
64
|
-
json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
|
65
|
-
)
|
66
|
-
|
67
|
-
requests_response = await self._http_delete(url, headers=self._headers())
|
68
|
-
await self._check_http_errors(requests_response)
|
69
|
-
|
70
|
-
@overload
|
71
57
|
async def post(
|
72
58
|
self,
|
73
59
|
endpoint: str,
|
@@ -76,60 +62,18 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
76
62
|
files: Optional[List[Tuple]] = None,
|
77
63
|
poll_result: bool = True,
|
78
64
|
url: Optional[str] = None,
|
79
|
-
*,
|
80
|
-
pangea_response: Literal[True] = True,
|
81
65
|
) -> PangeaResponse[TResult]:
|
82
|
-
"""
|
83
|
-
Makes a POST call to a Pangea Service endpoint.
|
66
|
+
"""Makes the POST call to a Pangea Service endpoint.
|
84
67
|
|
85
68
|
Args:
|
86
|
-
endpoint: The Pangea Service API endpoint.
|
87
|
-
data: The POST body payload object
|
69
|
+
endpoint(str): The Pangea Service API endpoint.
|
70
|
+
data(dict): The POST body payload object
|
88
71
|
|
89
72
|
Returns:
|
90
73
|
PangeaResponse which contains the response in its entirety and
|
91
74
|
various properties to retrieve individual fields
|
92
75
|
"""
|
93
76
|
|
94
|
-
@overload
|
95
|
-
async def post(
|
96
|
-
self,
|
97
|
-
endpoint: str,
|
98
|
-
result_class: Type[TResult],
|
99
|
-
data: str | BaseModel | Mapping[str, Any] | None = None,
|
100
|
-
files: Optional[List[Tuple]] = None,
|
101
|
-
poll_result: bool = True,
|
102
|
-
url: Optional[str] = None,
|
103
|
-
*,
|
104
|
-
pangea_response: Literal[False],
|
105
|
-
) -> TResult:
|
106
|
-
"""
|
107
|
-
Makes a POST call to a Pangea Service endpoint.
|
108
|
-
|
109
|
-
Args:
|
110
|
-
endpoint: The Pangea Service API endpoint.
|
111
|
-
data: The POST body payload object
|
112
|
-
"""
|
113
|
-
|
114
|
-
async def post(
|
115
|
-
self,
|
116
|
-
endpoint: str,
|
117
|
-
result_class: Type[TResult],
|
118
|
-
data: str | BaseModel | Mapping[str, Any] | None = None,
|
119
|
-
files: Optional[List[Tuple]] = None,
|
120
|
-
poll_result: bool = True,
|
121
|
-
url: Optional[str] = None,
|
122
|
-
*,
|
123
|
-
pangea_response: bool = True,
|
124
|
-
) -> PangeaResponse[TResult] | TResult:
|
125
|
-
"""
|
126
|
-
Makes a POST call to a Pangea Service endpoint.
|
127
|
-
|
128
|
-
Args:
|
129
|
-
endpoint: The Pangea Service API endpoint.
|
130
|
-
data: The POST body payload object
|
131
|
-
"""
|
132
|
-
|
133
77
|
if isinstance(data, BaseModel):
|
134
78
|
data = data.model_dump(exclude_none=True)
|
135
79
|
|
@@ -169,13 +113,9 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
169
113
|
|
170
114
|
await self._check_http_errors(requests_response)
|
171
115
|
|
172
|
-
if not pangea_response:
|
173
|
-
type_adapter = TypeAdapter(result_class)
|
174
|
-
return type_adapter.validate_python(await requests_response.json())
|
175
|
-
|
176
116
|
if "multipart/form-data" in requests_response.headers.get("content-type", ""):
|
177
117
|
multipart_response = await self._process_multipart_response(requests_response)
|
178
|
-
|
118
|
+
pangea_response: PangeaResponse = PangeaResponse(
|
179
119
|
requests_response,
|
180
120
|
result_class=result_class,
|
181
121
|
json=multipart_response.pangea_json,
|
@@ -188,110 +128,49 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
188
128
|
json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
|
189
129
|
)
|
190
130
|
|
191
|
-
|
131
|
+
pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
|
192
132
|
except aiohttp.ContentTypeError as e:
|
193
133
|
raise pe.PangeaException(
|
194
134
|
f"Failed to decode json response. {e}. Body: {await requests_response.text()}"
|
195
135
|
) from e
|
196
136
|
|
197
137
|
if poll_result:
|
198
|
-
|
138
|
+
pangea_response = await self._handle_queued_result(pangea_response)
|
199
139
|
|
200
|
-
return self._check_response(
|
140
|
+
return self._check_response(pangea_response)
|
201
141
|
|
202
|
-
|
203
|
-
|
204
|
-
self,
|
205
|
-
path: str,
|
206
|
-
result_class: Type[TResult],
|
207
|
-
check_response: bool = True,
|
208
|
-
*,
|
209
|
-
params: (
|
210
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
211
|
-
| None
|
212
|
-
) = None,
|
213
|
-
pangea_response: Literal[True] = True,
|
214
|
-
) -> PangeaResponse[TResult]:
|
215
|
-
"""
|
216
|
-
Makes the GET call to a Pangea Service endpoint.
|
142
|
+
async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
|
143
|
+
"""Makes the GET call to a Pangea Service endpoint.
|
217
144
|
|
218
145
|
Args:
|
219
|
-
|
220
|
-
|
146
|
+
endpoint(str): The Pangea Service API endpoint.
|
147
|
+
path(str): Additional URL path
|
221
148
|
|
222
149
|
Returns:
|
223
150
|
PangeaResponse which contains the response in its entirety and
|
224
151
|
various properties to retrieve individual fields
|
225
152
|
"""
|
226
153
|
|
227
|
-
@overload
|
228
|
-
async def get(
|
229
|
-
self,
|
230
|
-
path: str,
|
231
|
-
result_class: Type[TResult],
|
232
|
-
check_response: bool = True,
|
233
|
-
*,
|
234
|
-
params: (
|
235
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
236
|
-
| None
|
237
|
-
) = None,
|
238
|
-
pangea_response: Literal[False] = False,
|
239
|
-
) -> TResult:
|
240
|
-
"""
|
241
|
-
Makes the GET call to a Pangea Service endpoint.
|
242
|
-
|
243
|
-
Args:
|
244
|
-
path: Additional URL path
|
245
|
-
params: Dictionary of querystring data to attach to the request
|
246
|
-
"""
|
247
|
-
|
248
|
-
async def get(
|
249
|
-
self,
|
250
|
-
path: str,
|
251
|
-
result_class: Type[TResult],
|
252
|
-
check_response: bool = True,
|
253
|
-
*,
|
254
|
-
params: (
|
255
|
-
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
256
|
-
| None
|
257
|
-
) = None,
|
258
|
-
pangea_response: bool = True,
|
259
|
-
) -> PangeaResponse[TResult] | TResult:
|
260
|
-
"""
|
261
|
-
Makes the GET call to a Pangea Service endpoint.
|
262
|
-
|
263
|
-
Args:
|
264
|
-
path: Additional URL path
|
265
|
-
params: Dictionary of querystring data to attach to the request
|
266
|
-
pangea_response: Whether or not the response body follows Pangea's
|
267
|
-
standard response schema
|
268
|
-
"""
|
269
|
-
|
270
154
|
url = self._url(path)
|
271
155
|
self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
|
272
156
|
|
273
|
-
async with self.session.get(url,
|
157
|
+
async with self.session.get(url, headers=self._headers()) as requests_response:
|
274
158
|
await self._check_http_errors(requests_response)
|
275
|
-
|
276
|
-
if not pangea_response:
|
277
|
-
type_adapter = TypeAdapter(result_class)
|
278
|
-
return type_adapter.validate_python(await requests_response.json())
|
279
|
-
|
280
|
-
pangea_response_obj = PangeaResponse(
|
159
|
+
pangea_response = PangeaResponse(
|
281
160
|
requests_response, result_class=result_class, json=await requests_response.json()
|
282
161
|
)
|
283
162
|
|
284
163
|
self.logger.debug(
|
285
164
|
json.dumps(
|
286
|
-
{"service": self.service, "action": "get", "url": url, "response":
|
165
|
+
{"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
|
287
166
|
default=default_encoder,
|
288
167
|
)
|
289
168
|
)
|
290
169
|
|
291
170
|
if check_response is False:
|
292
|
-
return
|
171
|
+
return pangea_response
|
293
172
|
|
294
|
-
return self._check_response(
|
173
|
+
return self._check_response(pangea_response)
|
295
174
|
|
296
175
|
async def _check_http_errors(self, resp: aiohttp.ClientResponse):
|
297
176
|
if resp.status == 503:
|
@@ -425,14 +304,6 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
425
304
|
attached_files = await self._get_attached_files(multipart_reader)
|
426
305
|
return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
|
427
306
|
|
428
|
-
async def _http_delete(
|
429
|
-
self,
|
430
|
-
url: str,
|
431
|
-
*,
|
432
|
-
headers: Mapping[str, str | bytes | None] = {},
|
433
|
-
) -> aiohttp.ClientResponse:
|
434
|
-
return await self.session.delete(url, headers=headers)
|
435
|
-
|
436
307
|
async def _http_post(
|
437
308
|
self,
|
438
309
|
url: str,
|
@@ -595,13 +466,46 @@ class PangeaRequestAsync(PangeaRequestBase):
|
|
595
466
|
|
596
467
|
@override
|
597
468
|
def _init_session(self) -> aiohttp.ClientSession:
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
469
|
+
return aiohttp.ClientSession(middlewares=[self._retry_middleware])
|
470
|
+
|
471
|
+
def _calculate_retry_timeout(self, remaining_retries: int) -> float:
|
472
|
+
max_retries = self.config.request_retries
|
473
|
+
nb_retries = min(max_retries - remaining_retries, 1000)
|
474
|
+
sleep_seconds = min(self.config.request_backoff * pow(2.0, nb_retries), MAX_RETRY_DELAY)
|
475
|
+
jitter = 1 - 0.25 * random()
|
476
|
+
timeout = sleep_seconds * jitter
|
477
|
+
return max(timeout, 0)
|
478
|
+
|
479
|
+
async def _retry_middleware(
|
480
|
+
self, request: aiohttp.ClientRequest, handler: aiohttp.ClientHandlerType
|
481
|
+
) -> aiohttp.ClientResponse:
|
482
|
+
max_retries = self.config.request_retries
|
483
|
+
request_ids = set[str]()
|
484
|
+
retries_taken = 0
|
485
|
+
for retries_taken in range(max_retries + 1):
|
486
|
+
remaining_retries = max_retries - retries_taken
|
487
|
+
|
488
|
+
if len(request_ids) > 0:
|
489
|
+
request.headers["X-Pangea-Retried-Request-Ids"] = ",".join(request_ids)
|
490
|
+
|
491
|
+
response = await handler(request)
|
492
|
+
|
493
|
+
request_id = response.headers.get("x-request-id")
|
494
|
+
if request_id:
|
495
|
+
request_ids.add(request_id)
|
496
|
+
|
497
|
+
if not response.ok and remaining_retries > 0 and self._should_retry(response):
|
498
|
+
await self._sleep_for_retry(retries_taken=retries_taken, max_retries=max_retries)
|
499
|
+
continue
|
500
|
+
|
501
|
+
break
|
502
|
+
|
503
|
+
return response
|
504
|
+
|
505
|
+
def _should_retry(self, response: aiohttp.ClientResponse) -> bool:
|
506
|
+
return response.status in RETRYABLE_HTTP_CODES
|
507
|
+
|
508
|
+
async def _sleep_for_retry(self, *, retries_taken: int, max_retries: int) -> None:
|
509
|
+
remaining_retries = max_retries - retries_taken
|
510
|
+
timeout = self._calculate_retry_timeout(remaining_retries)
|
511
|
+
await asyncio.sleep(timeout)
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# ruff: noqa: F401
|
2
|
-
|
3
1
|
from .ai_guard import AIGuardAsync
|
4
2
|
from .audit import AuditAsync
|
5
3
|
from .authn import AuthNAsync
|
@@ -7,9 +5,27 @@ from .authz import AuthZAsync
|
|
7
5
|
from .embargo import EmbargoAsync
|
8
6
|
from .file_scan import FileScanAsync
|
9
7
|
from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
|
10
|
-
from .management import ManagementAsync
|
11
8
|
from .prompt_guard import PromptGuardAsync
|
12
9
|
from .redact import RedactAsync
|
13
10
|
from .sanitize import SanitizeAsync
|
14
11
|
from .share import ShareAsync
|
15
12
|
from .vault import VaultAsync
|
13
|
+
|
14
|
+
__all__ = (
|
15
|
+
"AIGuardAsync",
|
16
|
+
"AuditAsync",
|
17
|
+
"AuthNAsync",
|
18
|
+
"AuthZAsync",
|
19
|
+
"DomainIntelAsync",
|
20
|
+
"EmbargoAsync",
|
21
|
+
"FileIntelAsync",
|
22
|
+
"FileScanAsync",
|
23
|
+
"IpIntelAsync",
|
24
|
+
"PromptGuardAsync",
|
25
|
+
"RedactAsync",
|
26
|
+
"SanitizeAsync",
|
27
|
+
"ShareAsync",
|
28
|
+
"UrlIntelAsync",
|
29
|
+
"UserIntelAsync",
|
30
|
+
"VaultAsync",
|
31
|
+
)
|
@@ -1,30 +1,25 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from collections.abc import Mapping, Sequence
|
4
|
-
from typing import
|
4
|
+
from typing import overload
|
5
5
|
|
6
|
-
from typing_extensions import
|
6
|
+
from typing_extensions import Any, Literal
|
7
7
|
|
8
8
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
9
|
from pangea.config import PangeaConfig
|
10
10
|
from pangea.response import PangeaResponse
|
11
11
|
from pangea.services.ai_guard import (
|
12
|
-
AuditDataActivityConfig,
|
13
|
-
ConnectionsConfig,
|
14
12
|
ExtraInfo,
|
15
13
|
GuardResult,
|
16
14
|
LogFields,
|
15
|
+
McpToolsMessage,
|
17
16
|
Message,
|
18
17
|
Overrides,
|
19
|
-
RecipeConfig,
|
20
|
-
ServiceConfig,
|
21
|
-
ServiceConfigFilter,
|
22
|
-
ServiceConfigsPage,
|
23
18
|
TextGuardResult,
|
19
|
+
get_relevant_content,
|
20
|
+
patch_messages,
|
24
21
|
)
|
25
22
|
|
26
|
-
_T = TypeVar("_T")
|
27
|
-
|
28
23
|
|
29
24
|
class AIGuardAsync(ServiceBaseAsync):
|
30
25
|
"""AI Guard service client.
|
@@ -100,11 +95,12 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
100
95
|
async def guard_text(
|
101
96
|
self,
|
102
97
|
*,
|
103
|
-
messages: Sequence[Message],
|
98
|
+
messages: Sequence[Message | McpToolsMessage],
|
104
99
|
recipe: str | None = None,
|
105
100
|
debug: bool | None = None,
|
106
101
|
overrides: Overrides | None = None,
|
107
102
|
log_fields: LogFields | None = None,
|
103
|
+
only_relevant_content: bool = False,
|
108
104
|
) -> PangeaResponse[TextGuardResult]:
|
109
105
|
"""
|
110
106
|
Guard LLM input and output text
|
@@ -127,6 +123,8 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
127
123
|
recipe: Recipe key of a configuration of data types and settings
|
128
124
|
defined in the Pangea User Console. It specifies the rules that
|
129
125
|
are to be applied to the text, such as defang malicious URLs.
|
126
|
+
only_relevant_content: Whether or not to only send relevant content
|
127
|
+
to AI Guard.
|
130
128
|
|
131
129
|
Examples:
|
132
130
|
response = await ai_guard.guard_text(messages=[Message(role="user", content="hello world")])
|
@@ -136,11 +134,12 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
136
134
|
self,
|
137
135
|
text: str | None = None,
|
138
136
|
*,
|
139
|
-
messages: Sequence[Message] | None = None,
|
137
|
+
messages: Sequence[Message | McpToolsMessage] | None = None,
|
140
138
|
recipe: str | None = None,
|
141
139
|
debug: bool | None = None,
|
142
140
|
overrides: Overrides | None = None,
|
143
141
|
log_fields: LogFields | None = None,
|
142
|
+
only_relevant_content: bool = False,
|
144
143
|
) -> PangeaResponse[TextGuardResult]:
|
145
144
|
"""
|
146
145
|
Guard LLM input and output text
|
@@ -166,6 +165,8 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
166
165
|
recipe: Recipe key of a configuration of data types and settings
|
167
166
|
defined in the Pangea User Console. It specifies the rules that
|
168
167
|
are to be applied to the text, such as defang malicious URLs.
|
168
|
+
only_relevant_content: Whether or not to only send relevant content
|
169
|
+
to AI Guard.
|
169
170
|
|
170
171
|
Examples:
|
171
172
|
response = await ai_guard.guard_text("text")
|
@@ -174,7 +175,11 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
174
175
|
if text is not None and messages is not None:
|
175
176
|
raise ValueError("Exactly one of `text` or `messages` must be given")
|
176
177
|
|
177
|
-
|
178
|
+
if only_relevant_content and messages is not None:
|
179
|
+
original_messages = messages
|
180
|
+
messages, original_indices = get_relevant_content(messages)
|
181
|
+
|
182
|
+
response = await self.request.post(
|
178
183
|
"v1/text/guard",
|
179
184
|
TextGuardResult,
|
180
185
|
data={
|
@@ -187,6 +192,13 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
187
192
|
},
|
188
193
|
)
|
189
194
|
|
195
|
+
if only_relevant_content and response.result and response.result.prompt_messages:
|
196
|
+
response.result.prompt_messages = patch_messages(
|
197
|
+
original_messages, original_indices, response.result.prompt_messages
|
198
|
+
) # type: ignore[assignment]
|
199
|
+
|
200
|
+
return response
|
201
|
+
|
190
202
|
async def guard(
|
191
203
|
self,
|
192
204
|
input: Mapping[str, Any],
|
@@ -204,8 +216,8 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
204
216
|
source_ip: str | None = None,
|
205
217
|
source_location: str | None = None,
|
206
218
|
tenant_id: str | None = None,
|
207
|
-
event_type: Literal["input", "output"] | None = None,
|
208
|
-
|
219
|
+
event_type: Literal["input", "output", "tool_input", "tool_output", "tool_listing"] | None = None,
|
220
|
+
collector_instance_id: str | None = None,
|
209
221
|
extra_info: ExtraInfo | None = None,
|
210
222
|
count_tokens: bool | None = None,
|
211
223
|
) -> PangeaResponse[GuardResult]:
|
@@ -215,7 +227,7 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
215
227
|
Analyze and redact content to avoid manipulation of the model, addition
|
216
228
|
of malicious content, and other undesirable data transfers.
|
217
229
|
|
218
|
-
OperationId:
|
230
|
+
OperationId: ai_guard_post_v1_guard
|
219
231
|
|
220
232
|
Args:
|
221
233
|
input: 'messages' (required) contains Prompt content and role array
|
@@ -234,12 +246,12 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
234
246
|
source_location: Location of user or app or agent.
|
235
247
|
tenant_id: For gateway-like integrations with multi-tenant support.
|
236
248
|
event_type: (AIDR) Event Type.
|
237
|
-
|
249
|
+
collector_instance_id: (AIDR) collector instance id.
|
238
250
|
extra_info: (AIDR) Logging schema.
|
239
251
|
count_tokens: Provide input and output token count.
|
240
252
|
"""
|
241
253
|
return await self.request.post(
|
242
|
-
"
|
254
|
+
"v1/guard",
|
243
255
|
GuardResult,
|
244
256
|
data={
|
245
257
|
"input": input,
|
@@ -257,86 +269,8 @@ class AIGuardAsync(ServiceBaseAsync):
|
|
257
269
|
"source_location": source_location,
|
258
270
|
"tenant_id": tenant_id,
|
259
271
|
"event_type": event_type,
|
260
|
-
"
|
272
|
+
"collector_instance_id": collector_instance_id,
|
261
273
|
"extra_info": extra_info,
|
262
274
|
"count_tokens": count_tokens,
|
263
275
|
},
|
264
276
|
)
|
265
|
-
|
266
|
-
async def get_service_config(self, id: str) -> PangeaResponse[ServiceConfig]:
|
267
|
-
"""
|
268
|
-
OperationId: ai_guard_post_v1beta_config
|
269
|
-
"""
|
270
|
-
return await self.request.post("v1beta/config", data={"id": id}, result_class=ServiceConfig)
|
271
|
-
|
272
|
-
async def create_service_config(
|
273
|
-
self,
|
274
|
-
name: str,
|
275
|
-
*,
|
276
|
-
id: str | None = None,
|
277
|
-
audit_data_activity: AuditDataActivityConfig | None = None,
|
278
|
-
connections: ConnectionsConfig | None = None,
|
279
|
-
recipes: Mapping[str, RecipeConfig] | None = None,
|
280
|
-
) -> PangeaResponse[ServiceConfig]:
|
281
|
-
"""
|
282
|
-
OperationId: ai_guard_post_v1beta_config_create
|
283
|
-
"""
|
284
|
-
return await self.request.post(
|
285
|
-
"v1beta/config/create",
|
286
|
-
data={
|
287
|
-
"name": name,
|
288
|
-
"id": id,
|
289
|
-
"audit_data_activity": audit_data_activity,
|
290
|
-
"connections": connections,
|
291
|
-
"recipes": recipes,
|
292
|
-
},
|
293
|
-
result_class=ServiceConfig,
|
294
|
-
)
|
295
|
-
|
296
|
-
async def update_service_config(
|
297
|
-
self,
|
298
|
-
id: str,
|
299
|
-
name: str,
|
300
|
-
*,
|
301
|
-
audit_data_activity: AuditDataActivityConfig | None = None,
|
302
|
-
connections: ConnectionsConfig | None = None,
|
303
|
-
recipes: Mapping[str, RecipeConfig] | None = None,
|
304
|
-
) -> PangeaResponse[ServiceConfig]:
|
305
|
-
"""
|
306
|
-
OperationId: ai_guard_post_v1beta_config_update
|
307
|
-
"""
|
308
|
-
return await self.request.post(
|
309
|
-
"v1beta/config/update",
|
310
|
-
data={
|
311
|
-
"id": id,
|
312
|
-
"name": name,
|
313
|
-
"audit_data_activity": audit_data_activity,
|
314
|
-
"connections": connections,
|
315
|
-
"recipes": recipes,
|
316
|
-
},
|
317
|
-
result_class=ServiceConfig,
|
318
|
-
)
|
319
|
-
|
320
|
-
async def delete_service_config(self, id: str) -> PangeaResponse[ServiceConfig]:
|
321
|
-
"""
|
322
|
-
OperationId: ai_guard_post_v1beta_config_delete
|
323
|
-
"""
|
324
|
-
return await self.request.post("v1beta/config/delete", data={"id": id}, result_class=ServiceConfig)
|
325
|
-
|
326
|
-
async def list_service_configs(
|
327
|
-
self,
|
328
|
-
*,
|
329
|
-
filter: ServiceConfigFilter | None = None,
|
330
|
-
last: str | None = None,
|
331
|
-
order: Literal["asc", "desc"] | None = None,
|
332
|
-
order_by: Literal["id", "created_at", "updated_at"] | None = None,
|
333
|
-
size: int | None = None,
|
334
|
-
) -> PangeaResponse[ServiceConfigsPage]:
|
335
|
-
"""
|
336
|
-
OperationId: ai_guard_post_v1beta_config_list
|
337
|
-
"""
|
338
|
-
return await self.request.post(
|
339
|
-
"v1beta/config/list",
|
340
|
-
data={"filter": filter, "last": last, "order": order, "order_by": order_by, "size": size},
|
341
|
-
result_class=ServiceConfigsPage,
|
342
|
-
)
|