superwise-api 4.16.0__tar.gz → 4.17.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.
Files changed (76) hide show
  1. {superwise_api-4.16.0 → superwise_api-4.17.0}/PKG-INFO +2 -1
  2. {superwise_api-4.16.0 → superwise_api-4.17.0}/pyproject.toml +2 -1
  3. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/api_client.py +25 -11
  4. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/rest.py +26 -12
  5. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/agent.py +139 -17
  6. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/dataset.py +55 -1
  7. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/destination.py +47 -7
  8. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/agent/agent.py +34 -0
  9. superwise_api-4.17.0/superwise_api/models/agent/feedback.py +16 -0
  10. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/agent/playground.py +3 -0
  11. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dataset/dataset.py +4 -0
  12. superwise_api-4.17.0/superwise_api/models/destination/destination.py +82 -0
  13. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/integration/integration.py +2 -0
  14. superwise_api-4.16.0/superwise_api/models/agent/feedback.py +0 -41
  15. superwise_api-4.16.0/superwise_api/models/destination/destination.py +0 -37
  16. {superwise_api-4.16.0 → superwise_api-4.17.0}/README.md +0 -0
  17. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/__init__.py +0 -0
  18. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/__init__.py +0 -0
  19. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/api_response.py +0 -0
  20. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/configuration.py +0 -0
  21. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/exceptions.py +0 -0
  22. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/models/page.py +0 -0
  23. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/client/models/threshold_settings.py +0 -0
  24. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/config.py +0 -0
  25. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/__init__.py +0 -0
  26. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/base.py +0 -0
  27. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/dashboard.py +0 -0
  28. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/dashboard_item.py +0 -0
  29. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/dataset_source.py +0 -0
  30. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/guardrails.py +0 -0
  31. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/integration.py +0 -0
  32. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/knowledge.py +0 -0
  33. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/mcp.py +0 -0
  34. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/model.py +0 -0
  35. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/model_provider.py +0 -0
  36. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/policy.py +0 -0
  37. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/source.py +0 -0
  38. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/entities/tag.py +0 -0
  39. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/errors.py +0 -0
  40. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/__init__.py +0 -0
  41. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/agent/__init__.py +0 -0
  42. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/agent/flowise.py +0 -0
  43. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/context/__init__.py +0 -0
  44. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/context/context.py +0 -0
  45. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dashboard/__init__.py +0 -0
  46. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dashboard/dashboard.py +0 -0
  47. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dashboard/query.py +0 -0
  48. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dashboard_item/__init__.py +0 -0
  49. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dashboard_item/dashboard_item.py +0 -0
  50. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dataset/__init__.py +0 -0
  51. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dataset/dataset_schema.py +0 -0
  52. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dataset_source/__init__.py +0 -0
  53. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/dataset_source/dataset_source.py +0 -0
  54. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/destination/__init__.py +0 -0
  55. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/guardrails/__init__.py +0 -0
  56. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/guardrails/guardrails.py +0 -0
  57. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/integration/__init__.py +0 -0
  58. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/knowledge/__init__.py +0 -0
  59. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/knowledge/knowledge.py +0 -0
  60. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/mcp/__init__.py +0 -0
  61. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/mcp/mcp.py +0 -0
  62. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/model/__init__.py +0 -0
  63. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/model/model.py +0 -0
  64. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/model_provider/__init__.py +0 -0
  65. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/model_provider/model_provider.py +0 -0
  66. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/policy/__init__.py +0 -0
  67. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/policy/policy.py +0 -0
  68. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/policy/query.py +0 -0
  69. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/source/__init__.py +0 -0
  70. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/source/source.py +0 -0
  71. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/tag/__init__.py +0 -0
  72. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/tag/tag.py +0 -0
  73. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/tool/__init__.py +0 -0
  74. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/tool/tool.py +0 -0
  75. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/models/utils.py +0 -0
  76. {superwise_api-4.16.0 → superwise_api-4.17.0}/superwise_api/superwise_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: superwise-api
3
- Version: 4.16.0
3
+ Version: 4.17.0
4
4
  Summary: Superwise SDK for Python
5
5
  License: MIT
6
6
  Keywords: OpenAPI,SuperwiseSDK
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Dist: Pillow (>=10.0.0,<11.0.0)
16
16
  Requires-Dist: aenum (>=3.1.11)
17
+ Requires-Dist: jsonschema (>=4.26.0,<5.0.0)
17
18
  Requires-Dist: pydantic (>=2.10.4,<3.0.0)
18
19
  Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
19
20
  Requires-Dist: python-dateutil (>=2.8.2)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  packages = [{ include = "superwise_api", from = "." }]
3
- version = "4.16.0"
3
+ version = "4.17.0"
4
4
  include = ["superwise_api/client/py.typed"]
5
5
 
6
6
  [tool.poetry.dependencies]
@@ -11,6 +11,7 @@ aenum = ">=3.1.11"
11
11
  requests = "^2.32.0"
12
12
  pydantic-settings = "^2.7.0"
13
13
  Pillow = "^10.0.0"
14
+ jsonschema = "^4.26.0"
14
15
 
15
16
 
16
17
  [tool.poetry.group.dev.dependencies]
@@ -7,7 +7,7 @@ import re
7
7
  import tempfile
8
8
  import uuid
9
9
  from multiprocessing.pool import ThreadPool
10
- from typing import Optional
10
+
11
11
  from urllib.parse import quote
12
12
 
13
13
  from dateutil.parser import parse
@@ -774,7 +774,7 @@ class ApiClient:
774
774
  **kwargs,
775
775
  )
776
776
 
777
- def put(self, resource_path: str, data: Optional[dict] = None, **kwargs) -> BaseModel:
777
+ def put(self, resource_path: str, data: dict | None = None, **kwargs) -> BaseModel:
778
778
  _params = locals()
779
779
 
780
780
  _all_params = ["async_req", "_request_auth", "_content_type", "_headers", "response_types_map"]
@@ -860,11 +860,19 @@ class ApiClient:
860
860
  )
861
861
 
862
862
  def post(
863
- self, resource_path: str, data: Optional[dict] = None, query_params: dict | None = None, **kwargs
863
+ self, resource_path: str, data: dict | None = None, query_params: dict | None = None, **kwargs
864
864
  ) -> BaseModel:
865
865
  _params = locals()
866
866
 
867
- _all_params = ["files", "async_req", "_request_auth", "_content_type", "_headers", "response_types_map"]
867
+ _all_params = [
868
+ "files",
869
+ "async_req",
870
+ "_request_auth",
871
+ "_content_type",
872
+ "_headers",
873
+ "response_types_map",
874
+ "post_params",
875
+ ]
868
876
  if "model_name" in kwargs:
869
877
  _all_params.append("model_name")
870
878
 
@@ -884,18 +892,21 @@ class ApiClient:
884
892
  # process the header parameters
885
893
  _header_params = dict(_params.get("_headers", {}))
886
894
  # process the form parameters
887
- _form_params = []
895
+ _form_params = _params.get("post_params", [])
888
896
  _files = kwargs.get("files", {})
889
897
  # process the body parameter
890
898
  _body_params = None
891
- if data is not None:
899
+ if data is not None and not _form_params:
892
900
  _body_params = data
893
901
 
894
902
  # set the HTTP header `Accept`
895
903
  _header_params["Accept"] = self.select_header_accept(["application/json"])
896
904
 
897
905
  # set the HTTP header `Content-Type`
898
- _content_types_list = _params.get("_content_type", self.select_header_content_type(["application/json"]))
906
+ if _form_params:
907
+ _content_types_list = "multipart/form-data"
908
+ else:
909
+ _content_types_list = _params.get("_content_type", self.select_header_content_type(["application/json"]))
899
910
  if _content_types_list:
900
911
  _header_params["Content-Type"] = _content_types_list
901
912
 
@@ -924,7 +935,7 @@ class ApiClient:
924
935
  )
925
936
 
926
937
  def delete(
927
- self, resource_path: str, model_name: str, entity_id: str, query_params: Optional[dict] = None, **kwargs
938
+ self, resource_path: str, model_name: str, entity_id: str, query_params: dict | None = None, **kwargs
928
939
  ) -> BaseModel:
929
940
  _params = locals()
930
941
 
@@ -1151,14 +1162,16 @@ class ApiClient:
1151
1162
  self,
1152
1163
  resource_path: str,
1153
1164
  response_model,
1154
- data: Optional[dict] = None,
1165
+ data: dict | None = None,
1166
+ post_params: list | None = None,
1155
1167
  **kwargs,
1156
1168
  ):
1157
1169
  """Performs a streaming POST request and yields parsed response objects.
1158
1170
 
1159
1171
  :param resource_path: Path to method endpoint.
1160
1172
  :param response_model: Model class to deserialize each line to.
1161
- :param data: Request body data.
1173
+ :param data: Request body data (JSON).
1174
+ :param post_params: Multipart form-data fields (list of tuples). Mutually exclusive with data.
1162
1175
  :param kwargs: Additional arguments (_headers, _request_timeout, _request_auth).
1163
1176
  :yields: Parsed response_model instances.
1164
1177
  :raises StreamingError: If an error event is received in the stream.
@@ -1175,7 +1188,7 @@ class ApiClient:
1175
1188
 
1176
1189
  # Set content type and accept headers
1177
1190
  _header_params["Accept"] = "application/x-ndjson"
1178
- _header_params["Content-Type"] = "application/json"
1191
+ _header_params["Content-Type"] = "multipart/form-data" if post_params else "application/json"
1179
1192
 
1180
1193
  # Handle authentication
1181
1194
  _request_auth = _params.get("_request_auth")
@@ -1196,6 +1209,7 @@ class ApiClient:
1196
1209
  url,
1197
1210
  headers=_header_params,
1198
1211
  body=data,
1212
+ post_params=post_params,
1199
1213
  _request_timeout=_params.get("_request_timeout"),
1200
1214
  )
1201
1215
 
@@ -404,6 +404,7 @@ class RESTClientObject:
404
404
  url,
405
405
  headers=None,
406
406
  body=None,
407
+ post_params=None,
407
408
  _request_timeout=None,
408
409
  ):
409
410
  """Perform a streaming POST request.
@@ -411,6 +412,7 @@ class RESTClientObject:
411
412
  :param url: http request url
412
413
  :param headers: http request headers
413
414
  :param body: request json body
415
+ :param post_params: multipart/form-data fields (list of tuples)
414
416
  :param _request_timeout: timeout setting for this request
415
417
  :return: StreamingRESTResponse object for iterating over lines
416
418
  """
@@ -424,18 +426,30 @@ class RESTClientObject:
424
426
  timeout = urllib3.Timeout(connect=_request_timeout[0], read=_request_timeout[1])
425
427
 
426
428
  try:
427
- request_body = None
428
- if body is not None:
429
- request_body = json.dumps(body)
430
-
431
- r = self.pool_manager.request(
432
- "POST",
433
- url,
434
- body=request_body,
435
- preload_content=False,
436
- timeout=timeout,
437
- headers=headers,
438
- )
429
+ if post_params:
430
+ # Remove Content-Type so urllib3 sets it with the multipart boundary
431
+ headers = {k: v for k, v in headers.items() if k != "Content-Type"}
432
+ r = self.pool_manager.request(
433
+ "POST",
434
+ url,
435
+ fields=post_params,
436
+ encode_multipart=True,
437
+ preload_content=False,
438
+ timeout=timeout,
439
+ headers=headers,
440
+ )
441
+ else:
442
+ request_body = None
443
+ if body is not None:
444
+ request_body = json.dumps(body)
445
+ r = self.pool_manager.request(
446
+ "POST",
447
+ url,
448
+ body=request_body,
449
+ preload_content=False,
450
+ timeout=timeout,
451
+ headers=headers,
452
+ )
439
453
  except urllib3.exceptions.SSLError as e:
440
454
  msg = "{0}\n{1}".format(type(e).__name__, str(e))
441
455
  raise ApiException(status=0, reason=msg)
@@ -1,7 +1,10 @@
1
+ import io
2
+ import json
1
3
  from typing import Iterator
2
- from typing import Optional
4
+
3
5
  from typing import Sequence
4
6
 
7
+ from PIL import Image
5
8
  from pydantic import conint
6
9
 
7
10
  from superwise_api.client.api_client import ApiClient
@@ -42,6 +45,42 @@ class AgentApi(CoreEntitiyApi):
42
45
  super().__init__(api_client)
43
46
  self._playground_resource_path = "/v1/application-playground"
44
47
 
48
+ def _detect_image_type(self, image_bytes: bytes) -> tuple[str, str]:
49
+ try:
50
+ with Image.open(io.BytesIO(image_bytes)) as img:
51
+ format_name = img.format
52
+ if format_name is None:
53
+ raise ValueError("Could not detect image format")
54
+ format_map = {
55
+ "JPEG": ("image/jpeg", "jpg"),
56
+ "PNG": ("image/png", "png"),
57
+ "WEBP": ("image/webp", "webp"),
58
+ }
59
+ if format_name in format_map:
60
+ return format_map[format_name]
61
+ raise ValueError(f"Unsupported image format: {format_name}. Supported formats: JPEG, PNG, WEBP")
62
+ except ValueError:
63
+ raise
64
+ except Exception:
65
+ raise ValueError("Image type not allowed. Only PNG, JPEG, and WebP are supported.")
66
+
67
+ def _validate_images(self, images: list[bytes]) -> None:
68
+ if len(images) > 3:
69
+ raise ValueError(f"Maximum 3 images allowed, got {len(images)}")
70
+ for idx, img in enumerate(images):
71
+ if not isinstance(img, bytes):
72
+ raise ValueError(f"Image at index {idx} must be bytes, got {type(img)}")
73
+ if len(img) > 20 * 1024 * 1024:
74
+ raise ValueError(f"Image at index {idx} exceeds the 20MB size limit")
75
+ self._detect_image_type(img)
76
+
77
+ def _build_image_fields(self, images: list[bytes]) -> list[tuple]:
78
+ fields = []
79
+ for i, img in enumerate(images):
80
+ media_type, ext = self._detect_image_type(img)
81
+ fields.append(("images", (f"image{i}.{ext}", img, media_type)))
82
+ return fields
83
+
45
84
  def create(
46
85
  self,
47
86
  name: str,
@@ -228,7 +267,7 @@ class AgentApi(CoreEntitiyApi):
228
267
  )
229
268
 
230
269
  def get_versions(
231
- self, agent_id: str, name: Optional[str] = None, created_by: Optional[str] = None, **kwargs
270
+ self, agent_id: str, name: str | None = None, created_by: str | None = None, **kwargs
232
271
  ) -> list[Version]:
233
272
  """
234
273
  Retrieves agent entities.
@@ -260,12 +299,12 @@ class AgentApi(CoreEntitiyApi):
260
299
 
261
300
  def get(
262
301
  self,
263
- name: Optional[str] = None,
264
- created_by: Optional[str] = None,
265
- prompt: Optional[str] = None,
266
- dataset_id: Optional[str] = None,
267
- page: Optional[conint(strict=True, ge=1)] = None,
268
- size: Optional[conint(strict=True, le=500, ge=1)] = None,
302
+ name: str | None = None,
303
+ created_by: str | None = None,
304
+ prompt: str | None = None,
305
+ dataset_id: str | None = None,
306
+ page: conint(strict=True, ge=1) | None = None,
307
+ size: conint(strict=True, le=500, ge=1) | None = None,
269
308
  **kwargs,
270
309
  ) -> Page:
271
310
  """
@@ -337,7 +376,12 @@ class AgentApi(CoreEntitiyApi):
337
376
 
338
377
  @BaseApi.raise_exception
339
378
  def ask_playground(
340
- self, input: str, agent_config: AgentConfig, chat_history: Optional[Sequence[dict]] = None, **kwargs
379
+ self,
380
+ input: str,
381
+ agent_config: AgentConfig,
382
+ chat_history: Sequence[dict] | None = None,
383
+ images: list[bytes] | None = None,
384
+ **kwargs,
341
385
  ) -> AskResponsePayload:
342
386
  """
343
387
  Performs ask request in playground mode.
@@ -346,16 +390,31 @@ class AgentApi(CoreEntitiyApi):
346
390
  input (str): The input to the model.
347
391
  agent_config (AgentConfig): The type of the agent and connected tools/context.
348
392
  chat_history (Sequence[dict], optional): The chat history.
393
+ images (list[bytes], optional): Up to 3 images (PNG, JPEG, WEBP, max 20MB each).
349
394
 
350
395
  Returns:
351
396
  AskResponsePayload: The response payload.
352
397
  """
398
+ response_types_map = {"200": AskResponsePayload, "422": "HTTPValidationError"}
399
+ if images:
400
+ self._validate_images(images)
401
+ fields = [
402
+ ("input", input),
403
+ ("chat_history", json.dumps(chat_history or [])),
404
+ ("config", agent_config.model_dump_json(by_alias=True)),
405
+ ]
406
+ fields.extend(self._build_image_fields(images))
407
+ return self.api_client.post(
408
+ resource_path=self._playground_resource_path + "/ask",
409
+ post_params=fields,
410
+ response_types_map=response_types_map,
411
+ **kwargs,
412
+ )
353
413
  payload = {
354
414
  "config": agent_config,
355
415
  "input": input,
356
416
  "chat_history": chat_history or [],
357
417
  }
358
- response_types_map = {"200": AskResponsePayload, "422": "HTTPValidationError"}
359
418
  return self.api_client.post(
360
419
  resource_path=self._playground_resource_path + "/ask",
361
420
  data=payload,
@@ -369,9 +428,10 @@ class AgentApi(CoreEntitiyApi):
369
428
  agent_id: str,
370
429
  input: str,
371
430
  api_token: str | None = None,
372
- chat_history: Optional[Sequence[dict]] = None,
431
+ chat_history: Sequence[dict] | None = None,
373
432
  context_id: str | None = None,
374
433
  metadata: dict | None = None,
434
+ images: list[bytes] | None = None,
375
435
  **kwargs,
376
436
  ) -> AskResponsePayload:
377
437
  """
@@ -382,13 +442,35 @@ class AgentApi(CoreEntitiyApi):
382
442
  input (str): The input to the model.
383
443
  api_token (str): The API token of the agent.
384
444
  chat_history (Sequence[dict], optional): The chat history.
445
+ context_id (str, optional): The context ID for the conversation.
446
+ metadata (dict, optional): Additional metadata.
447
+ images (list[bytes], optional): Up to 3 images (PNG, JPEG, WEBP, max 20MB each).
385
448
 
386
449
  Returns:
387
450
  AskResponsePayload: The response payload.
388
451
  """
389
- payload = {"input": input, "chat_history": chat_history or [], "context_id": context_id, "metadata": metadata}
390
- headers = {"x-api-token": api_token} if api_token else {}
391
452
  response_types_map = {"200": AskResponsePayload, "422": "HTTPValidationError"}
453
+ if images:
454
+ self._validate_images(images)
455
+ headers = {"x-api-token": api_token} if api_token else {}
456
+ if images:
457
+ fields = [
458
+ ("input", input),
459
+ ("chat_history", json.dumps(chat_history or [])),
460
+ ]
461
+ if context_id is not None:
462
+ fields.append(("context_id", context_id))
463
+ if metadata is not None:
464
+ fields.append(("metadata", json.dumps(metadata)))
465
+ fields.extend(self._build_image_fields(images))
466
+ return self.api_client.post(
467
+ resource_path=f"/v1/app-worker/{agent_id}/v1/ask",
468
+ post_params=fields,
469
+ response_types_map=response_types_map,
470
+ _headers=headers,
471
+ **kwargs,
472
+ )
473
+ payload = {"input": input, "chat_history": chat_history or [], "context_id": context_id, "metadata": metadata}
392
474
  return self.api_client.post(
393
475
  resource_path=f"/v1/app-worker/{agent_id}/v1/ask",
394
476
  data=payload,
@@ -401,8 +483,9 @@ class AgentApi(CoreEntitiyApi):
401
483
  self,
402
484
  input: str,
403
485
  agent_config: AgentConfig,
404
- chat_history: Optional[Sequence[dict]] = None,
405
- guardrails: Optional[list[str]] = None,
486
+ chat_history: Sequence[dict] | None = None,
487
+ guardrails: list[str] | None = None,
488
+ images: list[bytes] | None = None,
406
489
  **kwargs,
407
490
  ) -> Iterator[StreamedResponse]:
408
491
  """
@@ -413,6 +496,7 @@ class AgentApi(CoreEntitiyApi):
413
496
  agent_config (AgentConfig): The type of the agent and connected tools/context.
414
497
  chat_history (Sequence[dict], optional): The chat history.
415
498
  guardrails (list[str], optional): List of guardrail IDs to apply.
499
+ images (list[bytes], optional): Up to 3 images (PNG, JPEG, WEBP, max 20MB each).
416
500
 
417
501
  Yields:
418
502
  StreamedResponse: Individual streaming events (system, guardrails, llm, tool, response).
@@ -420,6 +504,23 @@ class AgentApi(CoreEntitiyApi):
420
504
  Raises:
421
505
  StreamingError: If an error event is received in the stream.
422
506
  """
507
+ if images:
508
+ self._validate_images(images)
509
+ if images:
510
+ fields = [
511
+ ("input", input),
512
+ ("chat_history", json.dumps(chat_history or [])),
513
+ ("config", agent_config.model_dump_json(by_alias=True)),
514
+ ]
515
+ if guardrails:
516
+ fields.append(("guardrails", json.dumps(guardrails)))
517
+ fields.extend(self._build_image_fields(images))
518
+ return self.api_client.post_stream(
519
+ resource_path=self._playground_resource_path + "/ask/stream",
520
+ response_model=StreamedResponse,
521
+ post_params=fields,
522
+ **kwargs,
523
+ )
423
524
  payload = {
424
525
  "config": agent_config,
425
526
  "input": input,
@@ -439,9 +540,10 @@ class AgentApi(CoreEntitiyApi):
439
540
  agent_id: str,
440
541
  input: str,
441
542
  api_token: str | None = None,
442
- chat_history: Optional[Sequence[dict]] = None,
543
+ chat_history: Sequence[dict] | None = None,
443
544
  context_id: str | None = None,
444
545
  metadata: dict | None = None,
546
+ images: list[bytes] | None = None,
445
547
  **kwargs,
446
548
  ) -> Iterator[StreamedResponse]:
447
549
  """
@@ -454,6 +556,7 @@ class AgentApi(CoreEntitiyApi):
454
556
  chat_history (Sequence[dict], optional): The chat history.
455
557
  context_id (str, optional): The context ID for the conversation.
456
558
  metadata (dict, optional): Additional metadata.
559
+ images (list[bytes], optional): Up to 3 images (PNG, JPEG, WEBP, max 20MB each).
457
560
 
458
561
  Yields:
459
562
  StreamedResponse: Individual streaming events (system, guardrails, llm, tool, response).
@@ -461,8 +564,27 @@ class AgentApi(CoreEntitiyApi):
461
564
  Raises:
462
565
  StreamingError: If an error event is received in the stream.
463
566
  """
464
- payload = {"input": input, "chat_history": chat_history or [], "context_id": context_id, "metadata": metadata}
567
+ if images:
568
+ self._validate_images(images)
465
569
  headers = {"x-api-token": api_token} if api_token else {}
570
+ if images:
571
+ fields = [
572
+ ("input", input),
573
+ ("chat_history", json.dumps(chat_history or [])),
574
+ ]
575
+ if context_id is not None:
576
+ fields.append(("context_id", context_id))
577
+ if metadata is not None:
578
+ fields.append(("metadata", json.dumps(metadata)))
579
+ fields.extend(self._build_image_fields(images))
580
+ return self.api_client.post_stream(
581
+ resource_path=f"/v1/app-worker/{agent_id}/v1/ask/stream",
582
+ response_model=StreamedResponse,
583
+ post_params=fields,
584
+ _headers=headers,
585
+ **kwargs,
586
+ )
587
+ payload = {"input": input, "chat_history": chat_history or [], "context_id": context_id, "metadata": metadata}
466
588
  return self.api_client.post_stream(
467
589
  resource_path=f"/v1/app-worker/{agent_id}/v1/ask/stream",
468
590
  response_model=StreamedResponse,
@@ -5,7 +5,7 @@ from pydantic import conint
5
5
 
6
6
  from superwise_api.client.models.page import Page
7
7
  from superwise_api.entities.base import BaseApi, CoreEntitiyApi
8
- from superwise_api.models.dataset.dataset import Dataset, SqlQueryResponse
8
+ from superwise_api.models.dataset.dataset import Dataset, NaturalLanguageQueryResponse, SqlQueryResponse
9
9
  from superwise_api.models.dataset.dataset_schema import RecordLogMessage
10
10
  from superwise_api.models.dataset_source.dataset_source import DatasetSource
11
11
  from superwise_api.models.dataset_source.dataset_source import IngestType
@@ -288,6 +288,14 @@ class DatasetApi(CoreEntitiyApi):
288
288
  _preload_content=True,
289
289
  )
290
290
 
291
+ def update_record_in_dataset(self, dataset_id: str, key_field: str, key_value: str, record: dict, **kwargs):
292
+ """Update a record via ``POST /{dataset_id}/update`` (async; waits for row then publishes)."""
293
+ return self.api_client.post(
294
+ resource_path=f"{self._resource_path}/{dataset_id}/update",
295
+ data={"key_field": key_field, "key_value": key_value, "record": record},
296
+ **kwargs,
297
+ )
298
+
291
299
  def get_media(self, dataset_id: str, image_id: str, thumbnail: bool = False, **kwargs) -> tuple[str, bytes]:
292
300
  """Retrieve a single image from a dataset.
293
301
 
@@ -353,3 +361,49 @@ class DatasetApi(CoreEntitiyApi):
353
361
  response_types_map=response_types_map,
354
362
  **kwargs,
355
363
  )
364
+
365
+ def execute_nl_query(self, query: str, dataset_ids: list[str], **kwargs) -> NaturalLanguageQueryResponse:
366
+ """
367
+ Convert a natural language request to SQL and execute it.
368
+
369
+ Args:
370
+ query (str): Plain English description of the data you want.
371
+ dataset_ids (list[str]): Dataset IDs to scope the query.
372
+ **kwargs: Arbitrary keyword arguments passed to the API client.
373
+
374
+ Returns:
375
+ NaturalLanguageQueryResponse: Contains the generated SQL, a valid flag,
376
+ optional error message, and query data.
377
+ """
378
+ return self._post_nl_query(query=query, dataset_ids=dataset_ids, generate_only=False, **kwargs)
379
+
380
+ def generate_nl_query(self, query: str, dataset_ids: list[str], **kwargs) -> NaturalLanguageQueryResponse:
381
+ """
382
+ Convert a natural language request to SQL without executing it.
383
+
384
+ Args:
385
+ query (str): Plain English description of the data you want.
386
+ dataset_ids (list[str]): Dataset IDs to scope the query.
387
+ **kwargs: Arbitrary keyword arguments passed to the API client.
388
+
389
+ Returns:
390
+ NaturalLanguageQueryResponse: Contains the generated SQL and a valid flag.
391
+ Data is always None.
392
+ """
393
+ return self._post_nl_query(query=query, dataset_ids=dataset_ids, generate_only=True, **kwargs)
394
+
395
+ def _post_nl_query(
396
+ self, query: str, dataset_ids: list[str], generate_only: bool, **kwargs
397
+ ) -> NaturalLanguageQueryResponse:
398
+ response_types_map = {
399
+ "200": NaturalLanguageQueryResponse,
400
+ "400": None,
401
+ "422": None,
402
+ "500": None,
403
+ }
404
+ return self.api_client.post(
405
+ resource_path=self._resource_path + "/nl-query",
406
+ data={"query": query, "dataset_ids": dataset_ids, "generate_only": generate_only},
407
+ response_types_map=response_types_map,
408
+ **kwargs,
409
+ )
@@ -2,7 +2,14 @@ from typing import Optional
2
2
 
3
3
  from superwise_api.client.models.page import Page
4
4
  from superwise_api.entities.base import BaseApi
5
- from superwise_api.models.destination.destination import Destination, SlackDestinationParams
5
+ from superwise_api.models.destination.destination import (
6
+ Destination,
7
+ TeamsDestinationTestRequest,
8
+ SlackDestinationTestRequest,
9
+ EmailDestinationTestRequest,
10
+ DestinationParams,
11
+ DestinationType,
12
+ )
6
13
 
7
14
 
8
15
  class DestinationApi(BaseApi):
@@ -43,20 +50,34 @@ class DestinationApi(BaseApi):
43
50
  """
44
51
  return super().delete(_id=destination_id, **kwargs)
45
52
 
46
- def create(self, name: str, integration_id: str, params: SlackDestinationParams, **kwargs) -> dict:
53
+ def create(
54
+ self,
55
+ name: str,
56
+ integration_type: DestinationType,
57
+ params: DestinationParams,
58
+ integration_id: str | None = None,
59
+ **kwargs,
60
+ ) -> Destination:
47
61
  """
48
62
  Creates a new destination.
49
63
 
50
64
  Args:
51
65
  name (str): The name of the destination.
52
- integration_id (str): The id of the integration.
53
- params (dict): The parameters of the destination.
66
+ integration_type (str): The type of the integration.
67
+ params (DestinationParams): The parameters of the destination.
68
+ integration_id (str | None): The id of the integration. Required for Slack destinations.
54
69
  **kwargs: Arbitrary keyword arguments.
55
70
 
56
71
  Returns:
57
72
  Destination: The created destination.
58
73
  """
59
- data = {k: v for k, v in dict(name=name, integration_id=integration_id, params=params).items() if v is not None}
74
+ data = {
75
+ k: v
76
+ for k, v in dict(
77
+ name=name, integration_id=integration_id, integration_type=integration_type, params=params
78
+ ).items()
79
+ if v is not None
80
+ }
60
81
  return self.api_client.create(
61
82
  resource_path=self._resource_path, model_class=Destination, model_name=self._model_name, data=data, **kwargs
62
83
  )
@@ -106,14 +127,33 @@ class DestinationApi(BaseApi):
106
127
  **kwargs,
107
128
  )
108
129
 
130
+ def test_connection(
131
+ self,
132
+ request: TeamsDestinationTestRequest | SlackDestinationTestRequest | EmailDestinationTestRequest,
133
+ **kwargs,
134
+ ) -> None:
135
+ """
136
+ Test a webhook connection by sending a test notification.
137
+
138
+ Args:
139
+ request (TeamsDestinationTestRequest | SlackDestinationTestRequest | EmailDestinationTestRequest): The test connection request payload.
140
+ **kwargs: Arbitrary keyword arguments.
141
+ """
142
+ self.api_client.post(
143
+ resource_path=f"{self._resource_path}/test",
144
+ data=request.model_dump(),
145
+ response_types_map={"204": None},
146
+ **kwargs,
147
+ )
148
+
109
149
  def update(
110
150
  self,
111
151
  destination_id: str,
112
152
  *,
113
153
  name: Optional[str] = None,
114
- params: Optional[SlackDestinationParams] = None,
154
+ params: DestinationParams | None = None,
115
155
  **kwargs,
116
- ) -> dict:
156
+ ) -> Destination:
117
157
  """
118
158
  Updates a destination.
119
159
 
@@ -6,12 +6,15 @@ from typing import Literal
6
6
  from typing import Optional
7
7
  from typing import TypeAlias
8
8
 
9
+ from jsonschema import Draft7Validator, SchemaError
10
+
9
11
  from pydantic import BaseModel, ConfigDict
10
12
  from pydantic import Discriminator
11
13
  from pydantic import Field
12
14
  from pydantic import field_validator
13
15
  from pydantic import HttpUrl
14
16
  from pydantic import UUID4
17
+ import pydantic
15
18
 
16
19
  from superwise_api.models import SuperwiseEntity
17
20
  from superwise_api.models.agent.flowise import FlowiseCredentialUserInput
@@ -99,6 +102,15 @@ class AgentType(str, Enum):
99
102
  FLOWISE = "Flowise"
100
103
 
101
104
 
105
+ class ReactAgentResponseMode(str, Enum):
106
+ TEXT = "text"
107
+ JSON_SCHEMA = "json_schema"
108
+
109
+
110
+ class ReactAgentResponseJsonSchema(BaseModel):
111
+ model_config = ConfigDict(extra="allow")
112
+
113
+
102
114
  class SuperwiseConfig(SuperwiseEntity):
103
115
  framework: Literal[Framework.SUPERWISE.value] = Framework.SUPERWISE.value
104
116
  show_cites: bool = Field(default=False)
@@ -109,6 +121,28 @@ class SuperwiseConfig(SuperwiseEntity):
109
121
  class ReactAgentConfig(SuperwiseConfig):
110
122
  type: Literal[AgentType.REACT_AGENT.value] = AgentType.REACT_AGENT.value
111
123
  tools: List[ToolDef]
124
+ response_mode: ReactAgentResponseMode = Field(default=ReactAgentResponseMode.TEXT.value)
125
+ json_schema_response: ReactAgentResponseJsonSchema | None = None
126
+
127
+ @pydantic.model_validator(mode="after")
128
+ def validate_json_schema_response(self) -> "ReactAgentConfig":
129
+ if self.response_mode != ReactAgentResponseMode.JSON_SCHEMA:
130
+ return self
131
+
132
+ if self.json_schema_response is None:
133
+ raise ValueError("'json_schema_response' is required when response_mode is 'json_schema'")
134
+
135
+ schema_dict = self.json_schema_response.model_dump(exclude_none=True)
136
+
137
+ if "properties" not in schema_dict:
138
+ raise ValueError("'properties' is required in the schema")
139
+
140
+ try:
141
+ Draft7Validator.check_schema(schema_dict)
142
+ except SchemaError as e:
143
+ raise ValueError(f"Schema validation error: {e.message}") from e
144
+
145
+ return self
112
146
 
113
147
 
114
148
  AdvancedAgentConfig: TypeAlias = ReactAgentConfig
@@ -0,0 +1,16 @@
1
+ from uuid import UUID
2
+
3
+ from superwise_api.models import SuperwiseEntity
4
+
5
+
6
+ class EventFeedbackData(SuperwiseEntity):
7
+ interaction_id: UUID
8
+ question: str
9
+ answer: str
10
+ is_positive: bool
11
+ negative_feedback_category_harmful: bool
12
+ negative_feedback_category_untrue: bool
13
+ negative_feedback_category_unhelpful: bool
14
+ negative_feedback_category_other: bool
15
+ feedback_description: str
16
+ agent_id: str
@@ -70,6 +70,9 @@ class AskResponsePayload(SuperwiseEntity):
70
70
  metadata: Union[ResponseMetadata, Empty] = Field(
71
71
  default=Empty(), description="The metadata associated with the response."
72
72
  )
73
+ interaction_id: Optional[str] = Field(
74
+ default=None, description="Unique identifier for this interaction, used for feedback."
75
+ )
73
76
 
74
77
  @classmethod
75
78
  def from_dict(cls, data: dict) -> "AskResponsePayload":
@@ -88,3 +88,7 @@ class SqlQueryResponse(SuperwiseEntity):
88
88
  None,
89
89
  description="Query results. None when validate_only=True or when validation fails.",
90
90
  )
91
+
92
+
93
+ class NaturalLanguageQueryResponse(SqlQueryResponse):
94
+ sql: str | None = Field(None, description="The SQL generated from your natural language query")
@@ -0,0 +1,82 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Union, Annotated
4
+ from uuid import UUID
5
+
6
+ from pydantic import field_serializer, ConfigDict, BaseModel, Discriminator
7
+
8
+ from superwise_api.models import SuperwiseEntity
9
+
10
+
11
+ class DestinationType(str, Enum):
12
+ SLACK = "slack"
13
+ TEAMS = "teams"
14
+ EMAIL = "email"
15
+
16
+
17
+ class SlackDestinationParams(SuperwiseEntity):
18
+ channel_id: str
19
+
20
+
21
+ class TeamsDestinationParams(SuperwiseEntity):
22
+ webhook_url: str
23
+
24
+
25
+ class EmailDestinationParams(SuperwiseEntity):
26
+ emails: list[str]
27
+
28
+
29
+ class DestinationBase(BaseModel):
30
+ model_config = ConfigDict(use_enum_values=True)
31
+ integration_type: DestinationType
32
+
33
+
34
+ class TeamsDestinationTestRequest(TeamsDestinationParams, DestinationBase):
35
+ integration_type: DestinationType = DestinationType.TEAMS.value
36
+
37
+
38
+ class SlackDestinationTestRequest(SlackDestinationParams, DestinationBase):
39
+ integration_type: DestinationType = DestinationType.SLACK.value
40
+ integration_id: UUID
41
+
42
+
43
+ class EmailDestinationTestRequest(EmailDestinationParams, DestinationBase):
44
+ integration_type: DestinationType = DestinationType.EMAIL.value
45
+
46
+
47
+ DestinationParams = Union[SlackDestinationParams, TeamsDestinationParams, EmailDestinationParams]
48
+ DestinationTestRequest = Annotated[
49
+ TeamsDestinationTestRequest,
50
+ SlackDestinationTestRequest,
51
+ EmailDestinationTestRequest,
52
+ Discriminator("integration_type"),
53
+ ]
54
+
55
+
56
+ class Destination(SuperwiseEntity):
57
+ id: UUID
58
+ name: str
59
+ integration_id: UUID
60
+ params: SlackDestinationParams | TeamsDestinationParams | EmailDestinationParams
61
+ updated_at: datetime
62
+ created_at: datetime
63
+ created_by: str
64
+ tenant_id: str
65
+
66
+ @field_serializer("created_at", "updated_at")
67
+ def serialize_dt(self, dt: datetime) -> str:
68
+ return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
69
+
70
+ def to_dict(self) -> dict:
71
+ dict_ = self.model_dump(exclude_none=True)
72
+ return dict_
73
+
74
+ @classmethod
75
+ def from_dict(cls, dict_: dict) -> "Destination":
76
+ if "webhook_url" in dict_["params"]:
77
+ dict_["params"] = TeamsDestinationParams(**dict_["params"])
78
+ elif "emails" in dict_["params"]:
79
+ dict_["params"] = EmailDestinationParams(**dict_["params"])
80
+ else:
81
+ dict_["params"] = SlackDestinationParams(**dict_["params"])
82
+ return cls(**dict_)
@@ -7,6 +7,8 @@ from pydantic import BaseModel
7
7
 
8
8
  class IntegrationType(str, Enum):
9
9
  SLACK = "slack"
10
+ TEAMS = "teams"
11
+ EMAIL = "email"
10
12
 
11
13
 
12
14
  class Integration(BaseModel):
@@ -1,41 +0,0 @@
1
- from uuid import UUID, uuid4
2
-
3
- from superwise_api.models import SuperwiseEntity
4
-
5
-
6
- class EventFeedbackData(SuperwiseEntity):
7
- answer_id: UUID
8
- question: str
9
- answer: str
10
- is_positive: bool
11
- negative_feedback_category_harmful: bool
12
- negative_feedback_category_untrue: bool
13
- negative_feedback_category_unhelpful: bool
14
- negative_feedback_category_other: bool
15
- feedback_description: str
16
- agent_id: str
17
-
18
-
19
- def create_event_feedback(
20
- agent_id: str,
21
- question: str,
22
- answer: str,
23
- is_positive: bool = True,
24
- negative_feedback_category_harmful: bool = False,
25
- negative_feedback_category_untrue: bool = False,
26
- negative_feedback_category_unhelpful: bool = False,
27
- negative_feedback_category_other: bool = False,
28
- feedback_description: str = "",
29
- ) -> EventFeedbackData:
30
- return EventFeedbackData(
31
- agent_id=agent_id,
32
- answer_id=uuid4(),
33
- question=question,
34
- answer=answer,
35
- is_positive=is_positive,
36
- negative_feedback_category_harmful=negative_feedback_category_harmful,
37
- negative_feedback_category_untrue=negative_feedback_category_untrue,
38
- negative_feedback_category_unhelpful=negative_feedback_category_unhelpful,
39
- negative_feedback_category_other=negative_feedback_category_other,
40
- feedback_description=feedback_description,
41
- )
@@ -1,37 +0,0 @@
1
- from datetime import datetime
2
- from uuid import UUID
3
-
4
- from pydantic import field_serializer
5
-
6
- from superwise_api.models import SuperwiseEntity
7
-
8
-
9
- class SlackDestinationParams(SuperwiseEntity):
10
- channel_id: str
11
-
12
- def to_dict(self):
13
- return self.model_dump()
14
-
15
-
16
- class Destination(SuperwiseEntity):
17
- id: UUID
18
- name: str
19
- integration_id: UUID
20
- params: SlackDestinationParams
21
- updated_at: datetime
22
- created_at: datetime
23
- created_by: str
24
- tenant_id: str
25
-
26
- @field_serializer("created_at", "updated_at")
27
- def serialize_dt(self, dt: datetime) -> str:
28
- return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
29
-
30
- def to_dict(self) -> dict:
31
- dict_ = self.model_dump(exclude_none=True)
32
- return dict_
33
-
34
- @classmethod
35
- def from_dict(cls, dict_: dict) -> "Destination":
36
- dict_["params"] = SlackDestinationParams(**dict_["params"])
37
- return cls(**dict_)
File without changes