pygeai 0.6.0b6__py3-none-any.whl → 0.6.0b10__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.
Files changed (227) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/admin.rst +161 -0
  3. pygeai/_docs/source/content/api_reference/assistant.rst +326 -0
  4. pygeai/_docs/source/content/api_reference/auth.rst +379 -0
  5. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  6. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  7. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  8. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  9. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  10. pygeai/_docs/source/content/api_reference/health.rst +58 -0
  11. pygeai/_docs/source/content/api_reference/project.rst +20 -18
  12. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  13. pygeai/_docs/source/content/api_reference/rerank.rst +94 -0
  14. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  15. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  16. pygeai/_docs/source/content/api_reference.rst +13 -1
  17. pygeai/_docs/source/content/debugger.rst +376 -83
  18. pygeai/_docs/source/content/migration.rst +528 -0
  19. pygeai/_docs/source/content/modules.rst +1 -1
  20. pygeai/_docs/source/index.rst +59 -7
  21. pygeai/_docs/source/pygeai.auth.rst +29 -0
  22. pygeai/_docs/source/pygeai.cli.commands.rst +16 -0
  23. pygeai/_docs/source/pygeai.cli.rst +8 -0
  24. pygeai/_docs/source/pygeai.core.utils.rst +16 -0
  25. pygeai/_docs/source/pygeai.rst +1 -0
  26. pygeai/_docs/source/pygeai.tests.auth.rst +21 -0
  27. pygeai/_docs/source/pygeai.tests.cli.commands.rst +16 -0
  28. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  29. pygeai/_docs/source/pygeai.tests.core.base.rst +8 -0
  30. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  31. pygeai/_docs/source/pygeai.tests.core.files.rst +8 -0
  32. pygeai/_docs/source/pygeai.tests.core.plugins.rst +21 -0
  33. pygeai/_docs/source/pygeai.tests.core.rst +1 -0
  34. pygeai/_docs/source/pygeai.tests.evaluation.dataset.rst +21 -0
  35. pygeai/_docs/source/pygeai.tests.evaluation.plan.rst +21 -0
  36. pygeai/_docs/source/pygeai.tests.evaluation.result.rst +21 -0
  37. pygeai/_docs/source/pygeai.tests.evaluation.rst +20 -0
  38. pygeai/_docs/source/pygeai.tests.integration.lab.processes.rst +8 -0
  39. pygeai/_docs/source/pygeai.tests.organization.rst +8 -0
  40. pygeai/_docs/source/pygeai.tests.rst +2 -0
  41. pygeai/_docs/source/pygeai.tests.snippets.auth.rst +10 -0
  42. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  43. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  44. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  45. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  46. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  47. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  48. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  49. pygeai/_docs/source/pygeai.tests.snippets.organization.rst +40 -0
  50. pygeai/_docs/source/pygeai.tests.snippets.rst +2 -0
  51. pygeai/admin/clients.py +12 -32
  52. pygeai/assistant/clients.py +16 -44
  53. pygeai/assistant/data/clients.py +1 -0
  54. pygeai/assistant/data_analyst/clients.py +6 -13
  55. pygeai/assistant/rag/clients.py +24 -67
  56. pygeai/auth/clients.py +88 -14
  57. pygeai/auth/endpoints.py +4 -0
  58. pygeai/chat/clients.py +192 -25
  59. pygeai/chat/endpoints.py +2 -1
  60. pygeai/cli/commands/auth.py +178 -2
  61. pygeai/cli/commands/chat.py +227 -1
  62. pygeai/cli/commands/embeddings.py +56 -8
  63. pygeai/cli/commands/lab/ai_lab.py +0 -2
  64. pygeai/cli/commands/migrate.py +994 -434
  65. pygeai/cli/commands/organization.py +241 -0
  66. pygeai/cli/error_handler.py +116 -0
  67. pygeai/cli/geai.py +28 -10
  68. pygeai/cli/parsers.py +8 -2
  69. pygeai/core/base/clients.py +4 -1
  70. pygeai/core/common/exceptions.py +11 -10
  71. pygeai/core/embeddings/__init__.py +19 -0
  72. pygeai/core/embeddings/clients.py +20 -9
  73. pygeai/core/embeddings/mappers.py +16 -2
  74. pygeai/core/embeddings/responses.py +9 -2
  75. pygeai/core/feedback/clients.py +4 -8
  76. pygeai/core/files/clients.py +10 -25
  77. pygeai/core/files/managers.py +42 -0
  78. pygeai/core/llm/clients.py +11 -26
  79. pygeai/core/models.py +107 -0
  80. pygeai/core/plugins/clients.py +4 -7
  81. pygeai/core/rerank/clients.py +4 -8
  82. pygeai/core/secrets/clients.py +14 -37
  83. pygeai/core/services/rest.py +1 -1
  84. pygeai/core/utils/parsers.py +32 -0
  85. pygeai/core/utils/validators.py +10 -0
  86. pygeai/dbg/__init__.py +3 -0
  87. pygeai/dbg/debugger.py +565 -70
  88. pygeai/evaluation/clients.py +2 -1
  89. pygeai/evaluation/dataset/clients.py +46 -44
  90. pygeai/evaluation/plan/clients.py +28 -26
  91. pygeai/evaluation/result/clients.py +38 -5
  92. pygeai/gam/clients.py +10 -25
  93. pygeai/health/clients.py +4 -7
  94. pygeai/lab/agents/clients.py +21 -54
  95. pygeai/lab/agents/endpoints.py +2 -0
  96. pygeai/lab/clients.py +1 -0
  97. pygeai/lab/models.py +3 -3
  98. pygeai/lab/processes/clients.py +45 -127
  99. pygeai/lab/strategies/clients.py +11 -25
  100. pygeai/lab/tools/clients.py +23 -67
  101. pygeai/lab/tools/endpoints.py +3 -0
  102. pygeai/migration/__init__.py +31 -0
  103. pygeai/migration/strategies.py +404 -155
  104. pygeai/migration/tools.py +170 -3
  105. pygeai/organization/clients.py +135 -51
  106. pygeai/organization/endpoints.py +6 -1
  107. pygeai/organization/limits/clients.py +32 -91
  108. pygeai/organization/managers.py +157 -1
  109. pygeai/organization/mappers.py +76 -2
  110. pygeai/organization/responses.py +25 -1
  111. pygeai/proxy/clients.py +4 -1
  112. pygeai/tests/admin/test_clients.py +16 -11
  113. pygeai/tests/assistants/rag/test_clients.py +35 -23
  114. pygeai/tests/assistants/test_clients.py +22 -15
  115. pygeai/tests/auth/test_clients.py +191 -7
  116. pygeai/tests/chat/test_clients.py +211 -1
  117. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  118. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  119. pygeai/tests/cli/commands/test_migrate.py +112 -243
  120. pygeai/tests/cli/test_error_handler.py +225 -0
  121. pygeai/tests/cli/test_geai_driver.py +154 -0
  122. pygeai/tests/cli/test_parsers.py +5 -5
  123. pygeai/tests/core/embeddings/test_clients.py +144 -0
  124. pygeai/tests/core/embeddings/test_managers.py +171 -0
  125. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  126. pygeai/tests/core/feedback/test_clients.py +2 -0
  127. pygeai/tests/core/files/test_clients.py +1 -0
  128. pygeai/tests/core/llm/test_clients.py +14 -9
  129. pygeai/tests/core/plugins/test_clients.py +5 -3
  130. pygeai/tests/core/rerank/test_clients.py +1 -0
  131. pygeai/tests/core/secrets/test_clients.py +19 -13
  132. pygeai/tests/dbg/test_debugger.py +453 -75
  133. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  134. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  135. pygeai/tests/evaluation/result/test_clients.py +7 -5
  136. pygeai/tests/gam/test_clients.py +1 -1
  137. pygeai/tests/health/test_clients.py +1 -0
  138. pygeai/tests/lab/agents/test_clients.py +9 -0
  139. pygeai/tests/lab/processes/test_clients.py +36 -0
  140. pygeai/tests/lab/processes/test_mappers.py +3 -0
  141. pygeai/tests/lab/strategies/test_clients.py +14 -9
  142. pygeai/tests/migration/test_strategies.py +45 -218
  143. pygeai/tests/migration/test_tools.py +133 -9
  144. pygeai/tests/organization/limits/test_clients.py +17 -0
  145. pygeai/tests/organization/test_clients.py +206 -1
  146. pygeai/tests/organization/test_managers.py +122 -1
  147. pygeai/tests/proxy/test_clients.py +2 -0
  148. pygeai/tests/proxy/test_integration.py +1 -0
  149. pygeai/tests/snippets/auth/__init__.py +0 -0
  150. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  151. pygeai/tests/snippets/chat/get_response.py +15 -0
  152. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  153. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  154. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  155. pygeai/tests/snippets/dbg/__init__.py +0 -0
  156. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  157. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  158. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  159. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  160. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  161. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  162. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  163. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  164. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  165. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  166. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  167. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  168. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  169. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  170. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  171. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  172. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  173. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  174. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  175. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  176. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  177. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  178. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  179. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  180. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  181. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  182. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  183. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  184. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  185. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  186. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  187. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  188. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  189. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  190. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  191. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  192. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  193. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  194. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  195. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  196. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  197. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  198. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  199. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  200. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  201. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  202. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  203. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  204. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  205. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  206. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  207. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  208. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  209. pygeai/tests/snippets/migrate/__init__.py +45 -0
  210. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  211. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  212. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  213. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  214. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  215. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  216. pygeai/tests/snippets/organization/create_project.py +2 -2
  217. pygeai/tests/snippets/organization/get_memberships.py +12 -0
  218. pygeai/tests/snippets/organization/get_organization_members.py +6 -0
  219. pygeai/tests/snippets/organization/get_project_members.py +6 -0
  220. pygeai/tests/snippets/organization/get_project_memberships.py +12 -0
  221. pygeai/tests/snippets/organization/get_project_roles.py +6 -0
  222. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/METADATA +1 -1
  223. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/RECORD +227 -124
  224. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/WHEEL +0 -0
  225. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/entry_points.txt +0 -0
  226. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/licenses/LICENSE +0 -0
  227. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,11 @@
1
- import json
2
- from json import JSONDecodeError
3
1
  from typing import Any, Union
4
2
 
5
3
  from pygeai import logger
6
4
  from pygeai.core.base.clients import BaseClient
7
5
  from pygeai.core.common.exceptions import InvalidAPIResponseException
8
6
  from pygeai.core.feedback.endpoints import SEND_FEEDBACK_V1
7
+ from pygeai.core.utils.validators import validate_status_code
8
+ from pygeai.core.utils.parsers import parse_json_response
9
9
 
10
10
 
11
11
  class FeedbackClient(BaseClient):
@@ -45,10 +45,6 @@ class FeedbackClient(BaseClient):
45
45
  endpoint=endpoint,
46
46
  data=data
47
47
  )
48
- try:
49
- result = response.json()
50
- return result
51
- except JSONDecodeError as e:
52
- logger.error(f"Unable to send feedback. JSON parsing error: {e}. Response: {e}")
53
- raise InvalidAPIResponseException(f"Unable to send feedback for request {request_id}: {response.text}")
48
+ validate_status_code(response)
49
+ return parse_json_response(response, "send feedback. JSON parsing error")
54
50
 
@@ -7,6 +7,8 @@ from pygeai.core.base.clients import BaseClient
7
7
  from pygeai.core.common.exceptions import InvalidAPIResponseException
8
8
  from pygeai.core.files.endpoints import UPLOAD_FILE_V1, GET_FILE_V1, DELETE_FILE_V1, GET_FILE_CONTENT_V1, \
9
9
  GET_ALL_FILES_V1
10
+ from pygeai.core.utils.validators import validate_status_code
11
+ from pygeai.core.utils.parsers import parse_json_response
10
12
 
11
13
 
12
14
  class FileClient(BaseClient):
@@ -53,13 +55,8 @@ class FileClient(BaseClient):
53
55
  headers=headers,
54
56
  files=files
55
57
  )
56
- try:
57
- result = response.json()
58
- return result
59
- except JSONDecodeError as e:
60
- logger.error(
61
- f"Unable to upload file {file_name} in project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
62
- raise InvalidAPIResponseException(f"Unable to upload file {file_name}: {response.text}")
58
+ validate_status_code(response)
59
+ return parse_json_response(response, "upload file in project", file_name=file_name, project_id=project_id)
63
60
 
64
61
  finally:
65
62
  files["file"].close()
@@ -82,12 +79,8 @@ class FileClient(BaseClient):
82
79
  }
83
80
  )
84
81
  logger.debug(f"Retrieving file details for id {file_id}")
85
- try:
86
- result = response.json()
87
- return result
88
- except JSONDecodeError as e:
89
- logger.error(f"Unable to retrieve file details for id {file_id} in project {project}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
90
- raise InvalidAPIResponseException(f"Unable to retrieve file details for id {file_id}: {response.text}")
82
+ validate_status_code(response)
83
+ return parse_json_response(response, "retrieve file details for id in project", file_id=file_id, project=project)
91
84
 
92
85
  def delete_file(self, organization: str, project: str, file_id: str) -> dict:
93
86
  """
@@ -113,12 +106,8 @@ class FileClient(BaseClient):
113
106
  "project": project
114
107
  }
115
108
  )
116
- try:
117
- result = response.json()
118
- return result
119
- except JSONDecodeError as e:
120
- logger.error(f"Unable to delete file with id {file_id} in project {project}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
121
- raise InvalidAPIResponseException(f"Unable to delete file with id {file_id}: {response.text}")
109
+ validate_status_code(response)
110
+ return parse_json_response(response, "delete file with id in project", file_id=file_id, project=project)
122
111
 
123
112
  def get_file_content(self, organization: str, project: str, file_id: str) -> bytes:
124
113
  """
@@ -163,9 +152,5 @@ class FileClient(BaseClient):
163
152
  "project": project
164
153
  }
165
154
  )
166
- try:
167
- result = response.json()
168
- return result
169
- except JSONDecodeError as e:
170
- logger.error(f"Unable to retrieve file list for project {project}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
171
- raise InvalidAPIResponseException(f"Unable to retrieve file list for project {project}: {response.text}")
155
+ validate_status_code(response)
156
+ return parse_json_response(response, "retrieve file list for project", project=project)
@@ -1,3 +1,7 @@
1
+ import tempfile
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
1
5
  from pygeai import logger
2
6
  from pygeai.admin.clients import AdminClient
3
7
  from pygeai.core.base.mappers import ErrorMapper, ResponseMapper
@@ -180,3 +184,41 @@ class FileManager:
180
184
 
181
185
  result = FileResponseMapper.map_to_file_list_response(response_data)
182
186
  return result
187
+
188
+ def upload_file_from_content(
189
+ self,
190
+ file_name: str,
191
+ content: bytes,
192
+ folder: Optional[str] = None
193
+ ) -> UploadFileResponse:
194
+ """
195
+ Uploads a file from binary content to the specified organization and project.
196
+
197
+ This method creates a temporary file from the provided content, uploads it,
198
+ and then cleans up the temporary file.
199
+
200
+ :param file_name: str - The name for the uploaded file.
201
+ :param content: bytes - The binary content of the file.
202
+ :param folder: str, optional - Destination folder (default is None, which means temporary storage).
203
+ :return: UploadFileResponse - The response object containing the uploaded file details.
204
+ :raises APIError: If the API returns errors.
205
+ """
206
+ temp_file = None
207
+ try:
208
+ suffix = Path(file_name).suffix or ""
209
+ with tempfile.NamedTemporaryFile(mode='wb', suffix=suffix, delete=False) as temp_file:
210
+ temp_file.write(content)
211
+ temp_file_path = temp_file.name
212
+
213
+ upload_file = UploadFile(
214
+ path=temp_file_path,
215
+ name=file_name,
216
+ folder=folder
217
+ )
218
+ return self.upload_file(upload_file)
219
+ finally:
220
+ if temp_file:
221
+ try:
222
+ Path(temp_file_path).unlink(missing_ok=True)
223
+ except Exception as e:
224
+ logger.warning(f"Failed to delete temporary file {temp_file_path}: {e}")
@@ -1,11 +1,11 @@
1
- import json
2
- from json import JSONDecodeError
3
1
 
4
2
  from pygeai import logger
5
3
  from pygeai.core.base.clients import BaseClient
6
4
  from pygeai.core.common.exceptions import InvalidAPIResponseException
7
5
  from pygeai.core.llm.endpoints import GET_PROVIDER_LIST_V2, GET_PROVIDER_DATA_V2, GET_PROVIDER_MODELS_V2, \
8
6
  GET_MODEL_DATA_V2
7
+ from pygeai.core.utils.validators import validate_status_code
8
+ from pygeai.core.utils.parsers import parse_json_response
9
9
 
10
10
 
11
11
  class LlmClient(BaseClient):
@@ -13,12 +13,8 @@ class LlmClient(BaseClient):
13
13
  def get_provider_list(self) -> dict:
14
14
  logger.debug("Obtaining provider list")
15
15
  response = self.api_service.get(endpoint=GET_PROVIDER_LIST_V2)
16
- try:
17
- result = response.json()
18
- return result
19
- except JSONDecodeError as e:
20
- logger.error(f"Unable to obtain provider list: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
21
- raise InvalidAPIResponseException(f"Unable to obtain provider list: {response.text}")
16
+ validate_status_code(response)
17
+ return parse_json_response(response, "obtain provider list")
22
18
 
23
19
  def get_provider_data(self, provider_name: str) -> dict:
24
20
  endpoint = GET_PROVIDER_DATA_V2.format(providerName=provider_name)
@@ -26,12 +22,8 @@ class LlmClient(BaseClient):
26
22
  logger.debug(f"Obtaining provider data for {provider_name}")
27
23
 
28
24
  response = self.api_service.get(endpoint=endpoint)
29
- try:
30
- result = response.json()
31
- return result
32
- except JSONDecodeError as e:
33
- logger.error(f"Unable to obtain provider data for {provider_name}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
34
- raise InvalidAPIResponseException(f"Unable to obtain provider data for {provider_name}: {response.text}")
25
+ validate_status_code(response)
26
+ return parse_json_response(response, "obtain provider data", provider_name=provider_name)
35
27
 
36
28
  def get_provider_models(self, provider_name: str) -> dict:
37
29
  endpoint = GET_PROVIDER_MODELS_V2.format(providerName=provider_name)
@@ -39,12 +31,8 @@ class LlmClient(BaseClient):
39
31
  logger.debug(f"Obtaining provider models for {provider_name}")
40
32
 
41
33
  response = self.api_service.get(endpoint=endpoint)
42
- try:
43
- result = response.json()
44
- return result
45
- except JSONDecodeError as e:
46
- logger.error(f"Unable to obtain provider models for {provider_name}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
47
- raise InvalidAPIResponseException(f"Unable to obtain provider models for {provider_name}: {response.text}")
34
+ validate_status_code(response)
35
+ return parse_json_response(response, "obtain provider models", provider_name=provider_name)
48
36
 
49
37
  def get_model_data(
50
38
  self,
@@ -60,9 +48,6 @@ class LlmClient(BaseClient):
60
48
  logger.debug(f"Obtaining model data for {provider_name}/{model_name or model_id}")
61
49
 
62
50
  response = self.api_service.get(endpoint=endpoint)
63
- try:
64
- result = response.json()
65
- return result
66
- except JSONDecodeError as e:
67
- logger.error(f"Unable to obtain model data for {provider_name}/{model_name or model_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
68
- raise InvalidAPIResponseException(f"Unable to obtain model data for {provider_name}/{model_name or model_id}: {response.text}")
51
+ model_identifier = model_name or model_id
52
+ validate_status_code(response)
53
+ return parse_json_response(response, f"obtain model data for {provider_name}/{model_identifier}")
pygeai/core/models.py CHANGED
@@ -690,3 +690,110 @@ class DataAnalystAssistant(Assistant):
690
690
 
691
691
  class ChatWithDataAssistant(Assistant):
692
692
  type: Literal["ChatWithData"] = "ChatWithData"
693
+
694
+
695
+ class Role(CustomBaseModel):
696
+ """
697
+ {
698
+ "id": "string",
699
+ "name": "string",
700
+ "externalId": "string",
701
+ "type": "string",
702
+ "origin": "string"
703
+ }
704
+ """
705
+ id: str = Field(..., alias="id")
706
+ name: str = Field(..., alias="name")
707
+ external_id: Optional[str] = Field(None, alias="externalId")
708
+ type: Optional[str] = Field(None, alias="type")
709
+ origin: Optional[str] = Field(None, alias="origin")
710
+
711
+ def to_dict(self):
712
+ return self.model_dump(by_alias=True, exclude_none=True)
713
+
714
+ def __str__(self):
715
+ return str(self.to_dict())
716
+
717
+
718
+ class Member(CustomBaseModel):
719
+ """
720
+ {
721
+ "email": "string",
722
+ "roles": [...]
723
+ }
724
+ """
725
+ email: str = Field(..., alias="email")
726
+ roles: Optional[List[Role]] = Field(default_factory=list, alias="roles")
727
+
728
+ @field_validator("roles", mode="before")
729
+ @classmethod
730
+ def normalize_roles(cls, value):
731
+ if isinstance(value, list):
732
+ return [Role.model_validate(item) if isinstance(item, dict) else item for item in value]
733
+ return value
734
+
735
+ def to_dict(self):
736
+ return self.model_dump(by_alias=True, exclude_none=True)
737
+
738
+ def __str__(self):
739
+ return str(self.to_dict())
740
+
741
+
742
+ class ProjectMembership(CustomBaseModel):
743
+ """
744
+ {
745
+ "organizationId": "string",
746
+ "organizationName": "string",
747
+ "projectDescription": "string",
748
+ "projectId": "string",
749
+ "projectName": "string",
750
+ "roles": [...]
751
+ }
752
+ """
753
+ organization_id: Optional[str] = Field(None, alias="organizationId")
754
+ organization_name: Optional[str] = Field(None, alias="organizationName")
755
+ project_description: Optional[str] = Field(None, alias="projectDescription")
756
+ project_id: str = Field(..., alias="projectId")
757
+ project_name: str = Field(..., alias="projectName")
758
+ roles: Optional[List[Role]] = Field(default_factory=list, alias="roles")
759
+
760
+ @field_validator("roles", mode="before")
761
+ @classmethod
762
+ def normalize_roles(cls, value):
763
+ if isinstance(value, list):
764
+ return [Role.model_validate(item) if isinstance(item, dict) else item for item in value]
765
+ return value
766
+
767
+ def to_dict(self):
768
+ return self.model_dump(by_alias=True, exclude_none=True)
769
+
770
+ def __str__(self):
771
+ return str(self.to_dict())
772
+
773
+
774
+ class OrganizationMembership(CustomBaseModel):
775
+ """
776
+ {
777
+ "isStationAvailable": true,
778
+ "organizationId": "string",
779
+ "organizationName": "string",
780
+ "projects": [...]
781
+ }
782
+ """
783
+ is_station_available: Optional[bool] = Field(None, alias="isStationAvailable")
784
+ organization_id: str = Field(..., alias="organizationId")
785
+ organization_name: str = Field(..., alias="organizationName")
786
+ projects: Optional[List[ProjectMembership]] = Field(default_factory=list, alias="projects")
787
+
788
+ @field_validator("projects", mode="before")
789
+ @classmethod
790
+ def normalize_projects(cls, value):
791
+ if isinstance(value, list):
792
+ return [ProjectMembership.model_validate(item) if isinstance(item, dict) else item for item in value]
793
+ return value
794
+
795
+ def to_dict(self):
796
+ return self.model_dump(by_alias=True, exclude_none=True)
797
+
798
+ def __str__(self):
799
+ return str(self.to_dict())
@@ -1,10 +1,11 @@
1
- from json import JSONDecodeError
2
1
  from typing import Optional, List, Dict
3
2
 
4
3
  from pygeai import logger
5
4
  from pygeai.core.base.clients import BaseClient
6
5
  from pygeai.core.common.exceptions import InvalidAPIResponseException
7
6
  from pygeai.core.plugins.endpoints import LIST_ASSISTANTS_PLUGINS_V1
7
+ from pygeai.core.utils.validators import validate_status_code
8
+ from pygeai.core.utils.parsers import parse_json_response
8
9
 
9
10
 
10
11
  class PluginClient(BaseClient):
@@ -26,10 +27,6 @@ class PluginClient(BaseClient):
26
27
  endpoint=LIST_ASSISTANTS_PLUGINS_V1,
27
28
  params=params
28
29
  )
29
- try:
30
- result = response.json()
31
- return result
32
- except JSONDecodeError as e:
33
- logger.error(f"Unable to list assistants for organization {organization_id} and project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
34
- raise InvalidAPIResponseException(f"Unable to list assistants for organization {organization_id} and project {project_id}: {response.text}")
30
+ validate_status_code(response)
31
+ return parse_json_response(response, f"list assistants for organization {organization_id} and project {project_id}")
35
32
 
@@ -1,11 +1,11 @@
1
- import json
2
- from json import JSONDecodeError
3
1
  from typing import Any, Union
4
2
 
5
3
  from pygeai import logger
6
4
  from pygeai.core.base.clients import BaseClient
7
5
  from pygeai.core.common.exceptions import InvalidAPIResponseException
8
6
  from pygeai.core.rerank.endpoints import RERANK_V1
7
+ from pygeai.core.utils.validators import validate_status_code
8
+ from pygeai.core.utils.parsers import parse_json_response
9
9
 
10
10
 
11
11
  class RerankClient(BaseClient):
@@ -30,10 +30,6 @@ class RerankClient(BaseClient):
30
30
  endpoint=RERANK_V1,
31
31
  data=data
32
32
  )
33
- try:
34
- result = response.json()
35
- return result
36
- except JSONDecodeError as e:
37
- logger.error(f"Unable to rerank chunks for query '{query}' with model {model}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
38
- raise InvalidAPIResponseException(f"Unable to rerank chunks for query '{query}' with model {model}: {response.text}")
33
+ validate_status_code(response)
34
+ return parse_json_response(response, "rerank chunks for query with model", query=query, model=model)
39
35
 
@@ -1,4 +1,3 @@
1
- from json import JSONDecodeError
2
1
  from typing import Optional, List, Dict
3
2
 
4
3
  from pygeai import logger
@@ -6,6 +5,8 @@ from pygeai.core.base.clients import BaseClient
6
5
  from pygeai.core.common.exceptions import InvalidAPIResponseException
7
6
  from pygeai.core.secrets.endpoints import LIST_SECRETS_V1, GET_SECRET_V1, CREATE_SECRET_V1, UPDATE_SECRET_V1, \
8
7
  SET_SECRET_ACCESSES_V1, GET_SECRET_ACCESSES_V1
8
+ from pygeai.core.utils.validators import validate_status_code
9
+ from pygeai.core.utils.parsers import parse_json_response
9
10
 
10
11
 
11
12
  class SecretClient(BaseClient):
@@ -44,12 +45,8 @@ class SecretClient(BaseClient):
44
45
  endpoint=LIST_SECRETS_V1,
45
46
  params=params
46
47
  )
47
- try:
48
- result = response.json()
49
- return result
50
- except JSONDecodeError as e:
51
- logger.error(f"Unable to list secrets with params {params}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
52
- raise InvalidAPIResponseException(f"Unable to list secrets with params {params}: {response.text}")
48
+ validate_status_code(response)
49
+ return parse_json_response(response, "list secrets with params")
53
50
 
54
51
  def get_secret(self, secret_id: str) -> dict:
55
52
  """
@@ -69,12 +66,8 @@ class SecretClient(BaseClient):
69
66
  response = self.api_service.get(
70
67
  endpoint=endpoint
71
68
  )
72
- try:
73
- result = response.json()
74
- return result
75
- except JSONDecodeError as e:
76
- logger.error(f"Unable to get secret with ID '{secret_id}': JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
77
- raise InvalidAPIResponseException(f"Unable to get secret with ID '{secret_id}': {response.text}")
69
+ validate_status_code(response)
70
+ return parse_json_response(response, "get secret with ID", secret_id=secret_id)
78
71
 
79
72
  def create_secret(
80
73
  self,
@@ -111,12 +104,8 @@ class SecretClient(BaseClient):
111
104
  endpoint=CREATE_SECRET_V1,
112
105
  data=data
113
106
  )
114
- try:
115
- result = response.json()
116
- return result
117
- except JSONDecodeError as e:
118
- logger.error(f"Unable to create secret with name '{name}': JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
119
- raise InvalidAPIResponseException(f"Unable to create secret with name '{name}': {response.text}")
107
+ validate_status_code(response)
108
+ return parse_json_response(response, "create secret with name", name=name)
120
109
 
121
110
  def update_secret(
122
111
  self,
@@ -157,12 +146,8 @@ class SecretClient(BaseClient):
157
146
  endpoint=endpoint,
158
147
  data=data
159
148
  )
160
- try:
161
- result = response.json()
162
- return result
163
- except JSONDecodeError as e:
164
- logger.error(f"Unable to update secret with ID '{secret_id}': JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
165
- raise InvalidAPIResponseException(f"Unable to update secret with ID '{secret_id}': {response.text}")
149
+ validate_status_code(response)
150
+ return parse_json_response(response, "update secret with ID", secret_id=secret_id)
166
151
 
167
152
  def set_secret_accesses(
168
153
  self,
@@ -202,12 +187,8 @@ class SecretClient(BaseClient):
202
187
  endpoint=endpoint,
203
188
  data=data
204
189
  )
205
- try:
206
- result = response.json()
207
- return result
208
- except JSONDecodeError as e:
209
- logger.error(f"Unable to set accesses for secret with ID '{secret_id}': JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
210
- raise InvalidAPIResponseException(f"Unable to set accesses for secret with ID '{secret_id}': {response.text}")
190
+ validate_status_code(response)
191
+ return parse_json_response(response, "set accesses for secret with ID", secret_id=secret_id)
211
192
 
212
193
  def get_secret_accesses(self, secret_id: str) -> dict:
213
194
  """
@@ -227,9 +208,5 @@ class SecretClient(BaseClient):
227
208
  response = self.api_service.get(
228
209
  endpoint=endpoint
229
210
  )
230
- try:
231
- result = response.json()
232
- return result
233
- except JSONDecodeError as e:
234
- logger.error(f"Unable to get accesses for secret with ID '{secret_id}': JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
235
- raise InvalidAPIResponseException(f"Unable to get accesses for secret with ID '{secret_id}': {response.text}")
211
+ validate_status_code(response)
212
+ return parse_json_response(response, "get accesses for secret with ID", secret_id=secret_id)
@@ -388,7 +388,7 @@ class ApiService:
388
388
 
389
389
  def _add_endpoint_to_url(self, endpoint: str):
390
390
  clean_base_url = self.base_url.rstrip('/')
391
- url = f"{clean_base_url}/{endpoint}" if self._has_valid_protocol(clean_base_url) else f"https://{clean_base_url}/{endpoint}"
391
+ url = f"{clean_base_url}/{endpoint.lstrip('/')}" if self._has_valid_protocol(clean_base_url) else f"https://{clean_base_url}/{endpoint}"
392
392
  return url
393
393
 
394
394
  def _has_valid_protocol(self, url: str):
@@ -0,0 +1,32 @@
1
+ from json import JSONDecodeError
2
+
3
+ from pygeai import logger
4
+ from pygeai.core.common.exceptions import InvalidAPIResponseException
5
+
6
+
7
+ def parse_json_response(response, operation: str, **context):
8
+ """
9
+ Parse JSON response with standardized error handling.
10
+
11
+ :param response: HTTP response object
12
+ :param operation: Description of operation (e.g., "get project API token")
13
+ :param context: Additional context (e.g., api_token_id="123")
14
+ :return: Parsed JSON response
15
+ :raises InvalidAPIResponseException: If JSON parsing fails
16
+ """
17
+ try:
18
+ return response.json()
19
+ except JSONDecodeError as e:
20
+ full_msg = f"Unable to {operation}"
21
+ if context:
22
+ if len(context) == 1:
23
+ # Single context value: append as 'value'
24
+ value = list(context.values())[0]
25
+ full_msg += f" '{value}'"
26
+ else:
27
+ # Multiple context values: format as (key1='value1', key2='value2')
28
+ context_str = ", ".join([f"{k}='{v}'" for k, v in context.items()])
29
+ full_msg += f" ({context_str})"
30
+
31
+ logger.error(f"{full_msg}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
32
+ raise InvalidAPIResponseException(f"{full_msg}: {response.text}")
@@ -0,0 +1,10 @@
1
+ from pygeai import logger
2
+ from pygeai.core.common.exceptions import APIResponseError
3
+
4
+
5
+ def validate_status_code(response):
6
+ if response.status_code >= 300:
7
+ logger.error(f"Invalid status code returned from the API endpoint: {response.text}")
8
+ raise APIResponseError(f"API returned an error: {response.text}")
9
+
10
+
pygeai/dbg/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ from pygeai.dbg.debugger import Debugger, Breakpoint
2
+
3
+ __all__ = ['Debugger', 'Breakpoint']