pangea-sdk 6.4.0__py3-none-any.whl → 6.5.0b1__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "6.4.0"
1
+ __version__ = "6.5.0beta1"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
pangea/asyncio/request.py CHANGED
@@ -10,13 +10,13 @@ import asyncio
10
10
  import json
11
11
  import time
12
12
  from collections.abc import Iterable, Mapping
13
- from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
13
+ from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast, overload
14
14
 
15
15
  import aiohttp
16
16
  from aiohttp import FormData
17
- from pydantic import BaseModel
17
+ from pydantic import BaseModel, TypeAdapter
18
18
  from pydantic_core import to_jsonable_python
19
- from typing_extensions import Any, TypeAlias, TypeVar, override
19
+ from typing_extensions import Any, Literal, TypeAlias, TypeVar, override
20
20
 
21
21
  import pangea.exceptions as pe
22
22
  from pangea.request import MultipartResponse, PangeaRequestBase
@@ -50,6 +50,24 @@ class PangeaRequestAsync(PangeaRequestBase):
50
50
  be set in PangeaConfig.
51
51
  """
52
52
 
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
53
71
  async def post(
54
72
  self,
55
73
  endpoint: str,
@@ -58,18 +76,60 @@ class PangeaRequestAsync(PangeaRequestBase):
58
76
  files: Optional[List[Tuple]] = None,
59
77
  poll_result: bool = True,
60
78
  url: Optional[str] = None,
79
+ *,
80
+ pangea_response: Literal[True] = True,
61
81
  ) -> PangeaResponse[TResult]:
62
- """Makes the POST call to a Pangea Service endpoint.
82
+ """
83
+ Makes a POST call to a Pangea Service endpoint.
63
84
 
64
85
  Args:
65
- endpoint(str): The Pangea Service API endpoint.
66
- data(dict): The POST body payload object
86
+ endpoint: The Pangea Service API endpoint.
87
+ data: The POST body payload object
67
88
 
68
89
  Returns:
69
90
  PangeaResponse which contains the response in its entirety and
70
91
  various properties to retrieve individual fields
71
92
  """
72
93
 
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
+
73
133
  if isinstance(data, BaseModel):
74
134
  data = data.model_dump(exclude_none=True)
75
135
 
@@ -109,9 +169,13 @@ class PangeaRequestAsync(PangeaRequestBase):
109
169
 
110
170
  await self._check_http_errors(requests_response)
111
171
 
172
+ if not pangea_response:
173
+ type_adapter = TypeAdapter(result_class)
174
+ return type_adapter.validate_python(await requests_response.json())
175
+
112
176
  if "multipart/form-data" in requests_response.headers.get("content-type", ""):
113
177
  multipart_response = await self._process_multipart_response(requests_response)
114
- pangea_response: PangeaResponse = PangeaResponse(
178
+ pangea_response_obj: PangeaResponse = PangeaResponse(
115
179
  requests_response,
116
180
  result_class=result_class,
117
181
  json=multipart_response.pangea_json,
@@ -124,49 +188,110 @@ class PangeaRequestAsync(PangeaRequestBase):
124
188
  json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
125
189
  )
126
190
 
127
- pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
191
+ pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
128
192
  except aiohttp.ContentTypeError as e:
129
193
  raise pe.PangeaException(
130
194
  f"Failed to decode json response. {e}. Body: {await requests_response.text()}"
131
195
  ) from e
132
196
 
133
197
  if poll_result:
134
- pangea_response = await self._handle_queued_result(pangea_response)
198
+ pangea_response_obj = await self._handle_queued_result(pangea_response_obj)
135
199
 
136
- return self._check_response(pangea_response)
200
+ return self._check_response(pangea_response_obj)
137
201
 
138
- async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
139
- """Makes the GET call to a Pangea Service endpoint.
202
+ @overload
203
+ async def get(
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.
140
217
 
141
218
  Args:
142
- endpoint(str): The Pangea Service API endpoint.
143
- path(str): Additional URL path
219
+ path: Additional URL path
220
+ params: Dictionary of querystring data to attach to the request
144
221
 
145
222
  Returns:
146
223
  PangeaResponse which contains the response in its entirety and
147
224
  various properties to retrieve individual fields
148
225
  """
149
226
 
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
+
150
270
  url = self._url(path)
151
271
  self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
152
272
 
153
- async with self.session.get(url, headers=self._headers()) as requests_response:
273
+ async with self.session.get(url, params=params, headers=self._headers()) as requests_response:
154
274
  await self._check_http_errors(requests_response)
155
- pangea_response = PangeaResponse(
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(
156
281
  requests_response, result_class=result_class, json=await requests_response.json()
157
282
  )
158
283
 
159
284
  self.logger.debug(
160
285
  json.dumps(
161
- {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
286
+ {"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
162
287
  default=default_encoder,
163
288
  )
164
289
  )
165
290
 
166
291
  if check_response is False:
167
- return pangea_response
292
+ return pangea_response_obj
168
293
 
169
- return self._check_response(pangea_response)
294
+ return self._check_response(pangea_response_obj)
170
295
 
171
296
  async def _check_http_errors(self, resp: aiohttp.ClientResponse):
172
297
  if resp.status == 503:
@@ -300,6 +425,14 @@ class PangeaRequestAsync(PangeaRequestBase):
300
425
  attached_files = await self._get_attached_files(multipart_reader)
301
426
  return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
302
427
 
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
+
303
436
  async def _http_post(
304
437
  self,
305
438
  url: str,
@@ -7,6 +7,7 @@ from .authz import AuthZAsync
7
7
  from .embargo import EmbargoAsync
8
8
  from .file_scan import FileScanAsync
9
9
  from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
10
+ from .management import ManagementAsync
10
11
  from .prompt_guard import PromptGuardAsync
11
12
  from .redact import RedactAsync
12
13
  from .sanitize import SanitizeAsync
@@ -1,12 +1,29 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Sequence
4
- from typing import overload
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any, overload
5
+
6
+ from typing_extensions import Literal, TypeVar
5
7
 
6
8
  from pangea.asyncio.services.base import ServiceBaseAsync
7
9
  from pangea.config import PangeaConfig
8
10
  from pangea.response import PangeaResponse
9
- from pangea.services.ai_guard import LogFields, Message, Overrides, TextGuardResult
11
+ from pangea.services.ai_guard import (
12
+ AuditDataActivityConfig,
13
+ ConnectionsConfig,
14
+ ExtraInfo,
15
+ GuardResult,
16
+ LogFields,
17
+ Message,
18
+ Overrides,
19
+ RecipeConfig,
20
+ ServiceConfig,
21
+ ServiceConfigFilter,
22
+ ServiceConfigsPage,
23
+ TextGuardResult,
24
+ )
25
+
26
+ _T = TypeVar("_T")
10
27
 
11
28
 
12
29
  class AIGuardAsync(ServiceBaseAsync):
@@ -169,3 +186,157 @@ class AIGuardAsync(ServiceBaseAsync):
169
186
  "log_fields": log_fields,
170
187
  },
171
188
  )
189
+
190
+ async def guard(
191
+ self,
192
+ input: Mapping[str, Any],
193
+ *,
194
+ recipe: str | None = None,
195
+ debug: bool | None = None,
196
+ overrides: Overrides | None = None,
197
+ app_id: str | None = None,
198
+ actor_id: str | None = None,
199
+ llm_provider: str | None = None,
200
+ model: str | None = None,
201
+ model_version: str | None = None,
202
+ request_token_count: int | None = None,
203
+ response_token_count: int | None = None,
204
+ source_ip: str | None = None,
205
+ source_location: str | None = None,
206
+ tenant_id: str | None = None,
207
+ event_type: Literal["input", "output"] | None = None,
208
+ sensor_instance_id: str | None = None,
209
+ extra_info: ExtraInfo | None = None,
210
+ count_tokens: bool | None = None,
211
+ ) -> PangeaResponse[GuardResult]:
212
+ """
213
+ Guard LLM input and output
214
+
215
+ Analyze and redact content to avoid manipulation of the model, addition
216
+ of malicious content, and other undesirable data transfers.
217
+
218
+ OperationId: ai_guard_post_v1beta_guard
219
+
220
+ Args:
221
+ input: 'messages' (required) contains Prompt content and role array
222
+ in JSON format. The `content` is the multimodal text or image
223
+ input that will be analyzed. Additional properties such as
224
+ 'tools' may be provided for analysis.
225
+ recipe: Recipe key of a configuration of data types and settings defined in the Pangea User Console. It specifies the rules that are to be applied to the text, such as defang malicious URLs.
226
+ debug: Setting this value to true will provide a detailed analysis of the text data
227
+ app_name: Name of source application.
228
+ llm_provider: Underlying LLM. Example: 'OpenAI'.
229
+ model: Model used to perform the event. Example: 'gpt'.
230
+ model_version: Model version used to perform the event. Example: '3.5'.
231
+ request_token_count: Number of tokens in the request.
232
+ response_token_count: Number of tokens in the response.
233
+ source_ip: IP address of user or app or agent.
234
+ source_location: Location of user or app or agent.
235
+ tenant_id: For gateway-like integrations with multi-tenant support.
236
+ event_type: (AIDR) Event Type.
237
+ sensor_instance_id: (AIDR) sensor instance id.
238
+ extra_info: (AIDR) Logging schema.
239
+ count_tokens: Provide input and output token count.
240
+ """
241
+ return await self.request.post(
242
+ "v1beta/guard",
243
+ GuardResult,
244
+ data={
245
+ "input": input,
246
+ "recipe": recipe,
247
+ "debug": debug,
248
+ "overrides": overrides,
249
+ "app_id": app_id,
250
+ "actor_id": actor_id,
251
+ "llm_provider": llm_provider,
252
+ "model": model,
253
+ "model_version": model_version,
254
+ "request_token_count": request_token_count,
255
+ "response_token_count": response_token_count,
256
+ "source_ip": source_ip,
257
+ "source_location": source_location,
258
+ "tenant_id": tenant_id,
259
+ "event_type": event_type,
260
+ "sensor_instance_id": sensor_instance_id,
261
+ "extra_info": extra_info,
262
+ "count_tokens": count_tokens,
263
+ },
264
+ )
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
+ )