pygeai 0.6.0b7__py3-none-any.whl → 0.6.0b11__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 (178) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  3. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  4. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  5. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  6. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  7. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  8. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  9. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  10. pygeai/_docs/source/content/api_reference.rst +7 -0
  11. pygeai/_docs/source/content/debugger.rst +376 -83
  12. pygeai/_docs/source/content/migration.rst +528 -0
  13. pygeai/_docs/source/content/modules.rst +1 -1
  14. pygeai/_docs/source/pygeai.cli.rst +8 -0
  15. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  16. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  17. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  18. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  19. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  20. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  21. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  22. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  23. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  24. pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
  25. pygeai/admin/clients.py +5 -0
  26. pygeai/assistant/clients.py +7 -0
  27. pygeai/assistant/data_analyst/clients.py +2 -0
  28. pygeai/assistant/rag/clients.py +11 -0
  29. pygeai/chat/clients.py +236 -25
  30. pygeai/chat/endpoints.py +3 -1
  31. pygeai/cli/commands/chat.py +322 -1
  32. pygeai/cli/commands/embeddings.py +56 -8
  33. pygeai/cli/commands/migrate.py +994 -434
  34. pygeai/cli/error_handler.py +116 -0
  35. pygeai/cli/geai.py +28 -10
  36. pygeai/cli/parsers.py +8 -2
  37. pygeai/core/base/clients.py +3 -1
  38. pygeai/core/common/exceptions.py +11 -10
  39. pygeai/core/embeddings/__init__.py +19 -0
  40. pygeai/core/embeddings/clients.py +17 -2
  41. pygeai/core/embeddings/mappers.py +16 -2
  42. pygeai/core/embeddings/responses.py +9 -2
  43. pygeai/core/feedback/clients.py +1 -0
  44. pygeai/core/files/clients.py +5 -7
  45. pygeai/core/files/managers.py +42 -0
  46. pygeai/core/llm/clients.py +4 -0
  47. pygeai/core/plugins/clients.py +1 -0
  48. pygeai/core/rerank/clients.py +1 -0
  49. pygeai/core/secrets/clients.py +6 -0
  50. pygeai/core/services/rest.py +1 -1
  51. pygeai/dbg/__init__.py +3 -0
  52. pygeai/dbg/debugger.py +565 -70
  53. pygeai/evaluation/clients.py +1 -1
  54. pygeai/evaluation/dataset/clients.py +45 -44
  55. pygeai/evaluation/plan/clients.py +27 -26
  56. pygeai/evaluation/result/clients.py +37 -5
  57. pygeai/gam/clients.py +4 -0
  58. pygeai/health/clients.py +1 -0
  59. pygeai/lab/agents/clients.py +8 -1
  60. pygeai/lab/models.py +3 -3
  61. pygeai/lab/processes/clients.py +21 -0
  62. pygeai/lab/strategies/clients.py +4 -0
  63. pygeai/lab/tools/clients.py +1 -0
  64. pygeai/migration/__init__.py +31 -0
  65. pygeai/migration/strategies.py +404 -155
  66. pygeai/migration/tools.py +170 -3
  67. pygeai/organization/clients.py +13 -0
  68. pygeai/organization/limits/clients.py +15 -0
  69. pygeai/proxy/clients.py +3 -1
  70. pygeai/tests/admin/test_clients.py +16 -11
  71. pygeai/tests/assistants/rag/test_clients.py +35 -23
  72. pygeai/tests/assistants/test_clients.py +22 -15
  73. pygeai/tests/auth/test_clients.py +14 -6
  74. pygeai/tests/chat/test_clients.py +211 -1
  75. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  76. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  77. pygeai/tests/cli/commands/test_migrate.py +112 -243
  78. pygeai/tests/cli/test_error_handler.py +225 -0
  79. pygeai/tests/cli/test_geai_driver.py +154 -0
  80. pygeai/tests/cli/test_parsers.py +5 -5
  81. pygeai/tests/core/embeddings/test_clients.py +144 -0
  82. pygeai/tests/core/embeddings/test_managers.py +171 -0
  83. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  84. pygeai/tests/core/feedback/test_clients.py +2 -0
  85. pygeai/tests/core/files/test_clients.py +1 -0
  86. pygeai/tests/core/llm/test_clients.py +14 -9
  87. pygeai/tests/core/plugins/test_clients.py +5 -3
  88. pygeai/tests/core/rerank/test_clients.py +1 -0
  89. pygeai/tests/core/secrets/test_clients.py +19 -13
  90. pygeai/tests/dbg/test_debugger.py +453 -75
  91. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  92. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  93. pygeai/tests/evaluation/result/test_clients.py +7 -5
  94. pygeai/tests/gam/test_clients.py +1 -1
  95. pygeai/tests/health/test_clients.py +1 -0
  96. pygeai/tests/lab/agents/test_clients.py +9 -0
  97. pygeai/tests/lab/processes/test_clients.py +36 -0
  98. pygeai/tests/lab/processes/test_mappers.py +3 -0
  99. pygeai/tests/lab/strategies/test_clients.py +14 -9
  100. pygeai/tests/migration/test_strategies.py +45 -218
  101. pygeai/tests/migration/test_tools.py +133 -9
  102. pygeai/tests/organization/limits/test_clients.py +17 -0
  103. pygeai/tests/organization/test_clients.py +22 -0
  104. pygeai/tests/proxy/test_clients.py +2 -0
  105. pygeai/tests/proxy/test_integration.py +1 -0
  106. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  107. pygeai/tests/snippets/chat/get_response.py +15 -0
  108. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  109. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  110. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  111. pygeai/tests/snippets/dbg/__init__.py +0 -0
  112. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  113. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  114. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  115. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  116. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  117. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  118. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  119. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  120. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  121. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  122. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  123. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  124. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  125. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  126. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  127. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  128. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  129. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  130. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  131. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  132. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  133. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  134. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  135. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  136. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  137. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  138. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  139. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  140. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  141. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  142. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  143. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  144. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  145. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  146. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  147. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  148. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  149. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  150. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  151. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  152. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  153. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  154. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  155. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  156. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  157. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  158. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  159. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  160. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  161. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  162. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  163. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  164. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  165. pygeai/tests/snippets/migrate/__init__.py +45 -0
  166. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  167. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  168. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  169. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  170. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  171. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  172. pygeai/tests/snippets/organization/create_project.py +2 -2
  173. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/METADATA +1 -1
  174. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/RECORD +178 -96
  175. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/WHEEL +0 -0
  176. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/entry_points.txt +0 -0
  177. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/licenses/LICENSE +0 -0
  178. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ import traceback
2
+ from difflib import SequenceMatcher
3
+ from typing import List, Optional
4
+
5
+ from pygeai import logger
6
+ from pygeai.cli.commands import Command, Option
7
+ from pygeai.core.utils.console import Console
8
+
9
+
10
+ class ExitCode:
11
+ SUCCESS = 0
12
+ USER_INPUT_ERROR = 1
13
+ MISSING_REQUIREMENT = 2
14
+ SERVICE_ERROR = 3
15
+ KEYBOARD_INTERRUPT = 130
16
+ UNEXPECTED_ERROR = 255
17
+
18
+
19
+ class ErrorHandler:
20
+
21
+ @staticmethod
22
+ def format_error(error_type: str, message: str, suggestion: Optional[str] = None, show_help: bool = True) -> str:
23
+ output = f"ERROR: {message}"
24
+
25
+ if suggestion:
26
+ output += f"\n → {suggestion}"
27
+
28
+ if show_help:
29
+ output += "\n\nRun 'geai help' for usage information."
30
+
31
+ return output
32
+
33
+ @staticmethod
34
+ def find_similar_items(item: str, available_items: List[str], threshold: float = 0.6) -> List[str]:
35
+ similarities = []
36
+ for available in available_items:
37
+ ratio = SequenceMatcher(None, item.lower(), available.lower()).ratio()
38
+ if ratio >= threshold:
39
+ similarities.append((available, ratio))
40
+
41
+ similarities.sort(key=lambda x: x[1], reverse=True)
42
+ return [item[0] for item in similarities[:3]]
43
+
44
+ @staticmethod
45
+ def get_available_commands(commands: List[Command]) -> List[str]:
46
+ all_identifiers = []
47
+ for cmd in commands:
48
+ all_identifiers.extend(cmd.identifiers)
49
+ return all_identifiers
50
+
51
+ @staticmethod
52
+ def get_available_options(options: List[Option]) -> List[str]:
53
+ all_identifiers = []
54
+ for opt in options:
55
+ all_identifiers.extend(opt.identifiers)
56
+ return all_identifiers
57
+
58
+ @staticmethod
59
+ def handle_unknown_command(command: str, available_commands: List[Command]) -> str:
60
+ cmd_identifiers = ErrorHandler.get_available_commands(available_commands)
61
+ similar = ErrorHandler.find_similar_items(command, cmd_identifiers)
62
+
63
+ message = f"'{command}' is not a valid command."
64
+
65
+ if similar:
66
+ suggestion = f"Did you mean: {', '.join(similar)}?"
67
+ else:
68
+ suggestion = f"Available commands: {', '.join(sorted(set([cmd.identifiers[0] for cmd in available_commands])))}"
69
+
70
+ return ErrorHandler.format_error("Unknown Command", message, suggestion)
71
+
72
+ @staticmethod
73
+ def handle_unknown_option(option: str, available_options: List[Option]) -> str:
74
+ opt_identifiers = ErrorHandler.get_available_options(available_options)
75
+ similar = ErrorHandler.find_similar_items(option, opt_identifiers)
76
+
77
+ message = f"'{option}' is not a valid option."
78
+
79
+ if similar:
80
+ suggestion = f"Did you mean: {', '.join(similar)}?"
81
+ else:
82
+ suggestion = f"Available options: {', '.join(sorted(set([opt.identifiers[0] for opt in available_options])))}"
83
+
84
+ return ErrorHandler.format_error("Unknown Option", message, suggestion)
85
+
86
+ @staticmethod
87
+ def handle_missing_requirement(requirement_message: str) -> str:
88
+ message = requirement_message
89
+ suggestion = "Please provide all required parameters."
90
+ return ErrorHandler.format_error("Missing Requirement", message, suggestion)
91
+
92
+ @staticmethod
93
+ def handle_invalid_agent(error_message: str) -> str:
94
+ message = f"Failed to retrieve or validate the agent.\n Details: {error_message}"
95
+ suggestion = "Check your agent configuration and ensure the agent exists."
96
+ return ErrorHandler.format_error("Invalid Agent", message, suggestion)
97
+
98
+ @staticmethod
99
+ def handle_wrong_argument(error_message: str, usage: str) -> str:
100
+ Console.write_stderr(f"usage: {usage}")
101
+ message = error_message
102
+ suggestion = "Check the command syntax and try again."
103
+ return ErrorHandler.format_error("Invalid Argument", message, suggestion)
104
+
105
+ @staticmethod
106
+ def handle_keyboard_interrupt() -> str:
107
+ return "\n\nOperation cancelled by user."
108
+
109
+ @staticmethod
110
+ def handle_unexpected_error(exception: Exception) -> str:
111
+ logger.error(f"Unexpected error occurred: {exception}")
112
+ logger.error(traceback.format_exc())
113
+
114
+ message = "An unexpected error occurred. This may be a bug."
115
+ suggestion = f"Please report this issue to geai-sdk@globant.com with the following details:\n Error: {str(exception)}\n Run with geai-dbg for more details."
116
+ return ErrorHandler.format_error("Critical Error", message, suggestion, show_help=False)
pygeai/cli/geai.py CHANGED
@@ -5,6 +5,7 @@ from pygeai.cli.commands.base import base_commands, base_options
5
5
  from pygeai.cli.commands import ArgumentsEnum, Command
6
6
  from pygeai.cli.parsers import CommandParser
7
7
  from pygeai.cli.texts.help import CLI_USAGE
8
+ from pygeai.cli.error_handler import ErrorHandler, ExitCode
8
9
  from pygeai.core.base.session import get_session
9
10
  from pygeai.core.common.exceptions import UnknownArgumentError, MissingRequirementException, WrongArgumentError, \
10
11
  InvalidAgentException
@@ -64,19 +65,37 @@ class CLIDriver:
64
65
  arguments = []
65
66
 
66
67
  self.process_command(command, arguments)
67
- except (UnknownArgumentError, WrongArgumentError) as e:
68
- Console.write_stderr(f"usage: {CLI_USAGE}")
69
- Console.write_stderr(str(e))
70
- Console.write_stderr()
68
+ return ExitCode.SUCCESS
69
+ except UnknownArgumentError as e:
70
+ if hasattr(e, 'available_commands') and e.available_commands:
71
+ error_msg = ErrorHandler.handle_unknown_command(e.arg, e.available_commands)
72
+ elif hasattr(e, 'available_options') and e.available_options:
73
+ error_msg = ErrorHandler.handle_unknown_option(e.arg, e.available_options)
74
+ else:
75
+ error_msg = ErrorHandler.format_error("Unknown Argument", str(e))
76
+
77
+ Console.write_stderr(error_msg)
78
+ return ExitCode.USER_INPUT_ERROR
79
+ except WrongArgumentError as e:
80
+ error_msg = ErrorHandler.handle_wrong_argument(str(e), CLI_USAGE)
81
+ Console.write_stderr(error_msg)
82
+ return ExitCode.USER_INPUT_ERROR
71
83
  except MissingRequirementException as e:
72
- Console.write_stderr(f"ERROR: Something is missing! \nDetail: {e}")
84
+ error_msg = ErrorHandler.handle_missing_requirement(str(e))
85
+ Console.write_stderr(error_msg)
86
+ return ExitCode.MISSING_REQUIREMENT
73
87
  except InvalidAgentException as e:
74
- Console.write_stderr(f"ERROR: There was an error retrieving the agent. \nDetail: {e}")
88
+ error_msg = ErrorHandler.handle_invalid_agent(str(e))
89
+ Console.write_stderr(error_msg)
90
+ return ExitCode.SERVICE_ERROR
75
91
  except KeyboardInterrupt:
76
- Console.write_stdout()
92
+ message = ErrorHandler.handle_keyboard_interrupt()
93
+ Console.write_stdout(message)
94
+ return ExitCode.KEYBOARD_INTERRUPT
77
95
  except Exception as e:
78
- Console.write_stderr(f"CRITICAL: There has ben an unexpected error. Please report this bug to geai-sdk@globant.com. Error: {e}")
79
- return 255
96
+ error_msg = ErrorHandler.handle_unexpected_error(e)
97
+ Console.write_stderr(error_msg)
98
+ return ExitCode.UNEXPECTED_ERROR
80
99
 
81
100
  def process_command(self, command: Command, arguments: list[str]):
82
101
  """
@@ -107,4 +126,3 @@ class CLIDriver:
107
126
  else:
108
127
  option_list = CommandParser(None, subcommand.options).extract_option_list(subcommand_arguments)
109
128
  subcommand.action(option_list)
110
-
pygeai/cli/parsers.py CHANGED
@@ -18,7 +18,10 @@ class CommandParser:
18
18
  """
19
19
  command = self._get_associated_command(arg)
20
20
  if not command:
21
- raise UnknownArgumentError(f"'{arg}' is not a valid command. Run with help or h to check usage.")
21
+ error = UnknownArgumentError(f"'{arg}' is not a valid command.")
22
+ error.arg = arg
23
+ error.available_commands = self.available_commands
24
+ raise error
22
25
 
23
26
  return command
24
27
 
@@ -39,7 +42,10 @@ class CommandParser:
39
42
 
40
43
  flag = self._get_associated_option(arg)
41
44
  if not flag:
42
- raise UnknownArgumentError(f"'{arg}' is not a valid option. Run with help or h to check usage.")
45
+ error = UnknownArgumentError(f"'{arg}' is not a valid option.")
46
+ error.arg = arg
47
+ error.available_options = self.available_options
48
+ raise error
43
49
 
44
50
  if flag.requires_args:
45
51
  complementary_arg = True
@@ -20,7 +20,9 @@ class BaseClient(ABC):
20
20
  if not self.__session:
21
21
  raise MissingRequirementException("API KEY and BASE URL must be defined in order to use this functionality")
22
22
  elif api_key and base_url:
23
- self.__session = Session(api_key=api_key, base_url=base_url)
23
+ self.__session = get_session()
24
+ self.__session.api_key = api_key
25
+ self.__session.base_url = base_url
24
26
  else:
25
27
  self.__session = get_session()
26
28
 
@@ -6,55 +6,56 @@ class GEAIException(Exception):
6
6
 
7
7
 
8
8
  class UnknownArgumentError(GEAIException):
9
- """Argument provided is not valid"""
9
+ """Raised when an unknown or invalid command/option is provided"""
10
10
  pass
11
11
 
12
12
 
13
13
  class MissingRequirementException(GEAIException):
14
- """Requirement not available"""
14
+ """Raised when a required parameter or argument is missing"""
15
15
  pass
16
16
 
17
17
 
18
18
  class WrongArgumentError(GEAIException):
19
- """Wrongly formatted arguments"""
19
+ """Raised when arguments are incorrectly formatted or invalid"""
20
20
  pass
21
21
 
22
22
 
23
23
  class ServerResponseError(GEAIException):
24
- """There was an error in the request to the server"""
24
+ """Raised when the server returns an error response"""
25
25
  pass
26
26
 
27
27
 
28
28
  class APIError(GEAIException):
29
- """There was an error in the request to the server"""
29
+ """Raised when an API request fails"""
30
30
  pass
31
31
 
32
32
 
33
33
  class InvalidPathException(GEAIException):
34
- """There was an error trying to find the file in the specified path"""
34
+ """Raised when a file or directory path is invalid or not found"""
35
35
  pass
36
36
 
37
37
 
38
38
  class InvalidJSONException(GEAIException):
39
- """There was an error trying to load data from the JSON file"""
39
+ """Raised when JSON data cannot be parsed or is malformed"""
40
40
  pass
41
41
 
42
42
 
43
43
  class InvalidAPIResponseException(GEAIException):
44
- """There was an error handling response from the server"""
44
+ """Raised when the API response format is unexpected or invalid"""
45
45
  pass
46
46
 
47
47
 
48
48
  class InvalidResponseException(GEAIException):
49
- """There was an error getting a response from the server"""
49
+ """Raised when a response cannot be retrieved or processed"""
50
50
  pass
51
51
 
52
52
 
53
53
  class InvalidAgentException(GEAIException):
54
- """There was an error getting a response from the server"""
54
+ """Raised when an agent cannot be retrieved, validated, or is misconfigured"""
55
55
  pass
56
56
 
57
57
 
58
58
  class APIResponseError(GEAIException):
59
+ """Raised when there is an error in the API response"""
59
60
  pass
60
61
 
@@ -0,0 +1,19 @@
1
+ from pygeai.core.embeddings.clients import EmbeddingsClient
2
+ from pygeai.core.embeddings.managers import EmbeddingsManager
3
+ from pygeai.core.embeddings.models import EmbeddingConfiguration
4
+ from pygeai.core.embeddings.responses import (
5
+ EmbeddingResponse,
6
+ EmbeddingData,
7
+ UsageInfo,
8
+ TokenDetails
9
+ )
10
+
11
+ __all__ = [
12
+ 'EmbeddingsClient',
13
+ 'EmbeddingsManager',
14
+ 'EmbeddingConfiguration',
15
+ 'EmbeddingResponse',
16
+ 'EmbeddingData',
17
+ 'UsageInfo',
18
+ 'TokenDetails'
19
+ ]
@@ -1,7 +1,7 @@
1
1
 
2
2
  from pygeai import logger
3
3
  from pygeai.core.base.clients import BaseClient
4
- from pygeai.core.common.exceptions import InvalidAPIResponseException
4
+ from pygeai.core.common.exceptions import InvalidAPIResponseException, APIResponseError
5
5
  from pygeai.core.embeddings.endpoints import GENERATE_EMBEDDINGS
6
6
  from pygeai.core.utils.validators import validate_status_code
7
7
  from pygeai.core.utils.parsers import parse_json_response
@@ -43,7 +43,21 @@ class EmbeddingsClient(BaseClient):
43
43
 
44
44
  :return: dict - A dictionary containing the embedding results, including the model used, the generated
45
45
  embedding vectors, and usage statistics.
46
+ :raises ValueError: If validation fails for input parameters.
46
47
  """
48
+ if not input_list or len(input_list) == 0:
49
+ raise ValueError("input_list cannot be empty")
50
+
51
+ for idx, inp in enumerate(input_list):
52
+ if not inp or (isinstance(inp, str) and inp.strip() == ""):
53
+ raise ValueError(f"Input at index {idx} cannot be empty")
54
+
55
+ if encoding_format is not None and encoding_format not in ['float', 'base64']:
56
+ raise ValueError("encoding_format must be either 'float' or 'base64'")
57
+
58
+ if dimensions is not None and dimensions <= 0:
59
+ raise ValueError("dimensions must be a positive integer")
60
+
47
61
  data = {
48
62
  'model': model,
49
63
  'input': input_list,
@@ -74,5 +88,6 @@ class EmbeddingsClient(BaseClient):
74
88
  data=data,
75
89
  headers=headers
76
90
  )
91
+ validate_status_code(response)
77
92
  return parse_json_response(response, "generate embeddings")
78
-
93
+
@@ -1,4 +1,4 @@
1
- from pygeai.core.embeddings.responses import EmbeddingResponse, EmbeddingData, UsageInfo
1
+ from pygeai.core.embeddings.responses import EmbeddingResponse, EmbeddingData, UsageInfo, TokenDetails
2
2
 
3
3
 
4
4
  class EmbeddingsResponseMapper:
@@ -29,10 +29,24 @@ class EmbeddingsResponseMapper:
29
29
 
30
30
  @classmethod
31
31
  def map_to_usage_info(cls, data: dict) -> UsageInfo:
32
+ completion_tokens_details_data = data.get('completion_tokens_details')
33
+ prompt_tokens_details_data = data.get('prompt_tokens_details')
34
+
32
35
  return UsageInfo(
33
36
  prompt_tokens=data.get('prompt_tokens', 0),
34
37
  total_cost=data.get('total_cost', 0.0),
35
38
  total_tokens=data.get('total_tokens', 0),
36
39
  currency=data.get('currency', ''),
37
- prompt_cost=data.get('prompt_cost', 0.0)
40
+ prompt_cost=data.get('prompt_cost', 0.0),
41
+ completion_tokens_details=cls._parse_token_details(completion_tokens_details_data),
42
+ prompt_tokens_details=cls._parse_token_details(prompt_tokens_details_data)
43
+ )
44
+
45
+ @classmethod
46
+ def _parse_token_details(cls, data: dict) -> TokenDetails | None:
47
+ if data is None:
48
+ return None
49
+ return TokenDetails(
50
+ reasoning_tokens=data.get('reasoning_tokens'),
51
+ cached_tokens=data.get('cached_tokens')
38
52
  )
@@ -1,11 +1,16 @@
1
- from typing import List
1
+ from typing import List, Union, Optional
2
2
 
3
3
  from pydantic.main import BaseModel
4
4
 
5
5
 
6
+ class TokenDetails(BaseModel):
7
+ reasoning_tokens: Optional[int] = None
8
+ cached_tokens: Optional[int] = None
9
+
10
+
6
11
  class EmbeddingData(BaseModel):
7
12
  index: int
8
- embedding: List[float]
13
+ embedding: Union[List[float], str]
9
14
  object: str
10
15
 
11
16
 
@@ -15,6 +20,8 @@ class UsageInfo(BaseModel):
15
20
  total_tokens: int
16
21
  currency: str
17
22
  prompt_cost: float
23
+ completion_tokens_details: Optional[TokenDetails] = None
24
+ prompt_tokens_details: Optional[TokenDetails] = None
18
25
 
19
26
 
20
27
  class EmbeddingResponse(BaseModel):
@@ -45,5 +45,6 @@ class FeedbackClient(BaseClient):
45
45
  endpoint=endpoint,
46
46
  data=data
47
47
  )
48
+ validate_status_code(response)
48
49
  return parse_json_response(response, "send feedback. JSON parsing error")
49
50
 
@@ -55,13 +55,8 @@ class FileClient(BaseClient):
55
55
  headers=headers,
56
56
  files=files
57
57
  )
58
- try:
59
- result = response.json()
60
- return result
61
- except JSONDecodeError as e:
62
- logger.error(
63
- f"Unable to upload file {file_name} in project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
64
- 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)
65
60
 
66
61
  finally:
67
62
  files["file"].close()
@@ -84,6 +79,7 @@ class FileClient(BaseClient):
84
79
  }
85
80
  )
86
81
  logger.debug(f"Retrieving file details for id {file_id}")
82
+ validate_status_code(response)
87
83
  return parse_json_response(response, "retrieve file details for id in project", file_id=file_id, project=project)
88
84
 
89
85
  def delete_file(self, organization: str, project: str, file_id: str) -> dict:
@@ -110,6 +106,7 @@ class FileClient(BaseClient):
110
106
  "project": project
111
107
  }
112
108
  )
109
+ validate_status_code(response)
113
110
  return parse_json_response(response, "delete file with id in project", file_id=file_id, project=project)
114
111
 
115
112
  def get_file_content(self, organization: str, project: str, file_id: str) -> bytes:
@@ -155,4 +152,5 @@ class FileClient(BaseClient):
155
152
  "project": project
156
153
  }
157
154
  )
155
+ validate_status_code(response)
158
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}")
@@ -13,6 +13,7 @@ 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
+ validate_status_code(response)
16
17
  return parse_json_response(response, "obtain provider list")
17
18
 
18
19
  def get_provider_data(self, provider_name: str) -> dict:
@@ -21,6 +22,7 @@ class LlmClient(BaseClient):
21
22
  logger.debug(f"Obtaining provider data for {provider_name}")
22
23
 
23
24
  response = self.api_service.get(endpoint=endpoint)
25
+ validate_status_code(response)
24
26
  return parse_json_response(response, "obtain provider data", provider_name=provider_name)
25
27
 
26
28
  def get_provider_models(self, provider_name: str) -> dict:
@@ -29,6 +31,7 @@ class LlmClient(BaseClient):
29
31
  logger.debug(f"Obtaining provider models for {provider_name}")
30
32
 
31
33
  response = self.api_service.get(endpoint=endpoint)
34
+ validate_status_code(response)
32
35
  return parse_json_response(response, "obtain provider models", provider_name=provider_name)
33
36
 
34
37
  def get_model_data(
@@ -46,4 +49,5 @@ class LlmClient(BaseClient):
46
49
 
47
50
  response = self.api_service.get(endpoint=endpoint)
48
51
  model_identifier = model_name or model_id
52
+ validate_status_code(response)
49
53
  return parse_json_response(response, f"obtain model data for {provider_name}/{model_identifier}")
@@ -27,5 +27,6 @@ class PluginClient(BaseClient):
27
27
  endpoint=LIST_ASSISTANTS_PLUGINS_V1,
28
28
  params=params
29
29
  )
30
+ validate_status_code(response)
30
31
  return parse_json_response(response, f"list assistants for organization {organization_id} and project {project_id}")
31
32
 
@@ -30,5 +30,6 @@ class RerankClient(BaseClient):
30
30
  endpoint=RERANK_V1,
31
31
  data=data
32
32
  )
33
+ validate_status_code(response)
33
34
  return parse_json_response(response, "rerank chunks for query with model", query=query, model=model)
34
35
 
@@ -45,6 +45,7 @@ class SecretClient(BaseClient):
45
45
  endpoint=LIST_SECRETS_V1,
46
46
  params=params
47
47
  )
48
+ validate_status_code(response)
48
49
  return parse_json_response(response, "list secrets with params")
49
50
 
50
51
  def get_secret(self, secret_id: str) -> dict:
@@ -65,6 +66,7 @@ class SecretClient(BaseClient):
65
66
  response = self.api_service.get(
66
67
  endpoint=endpoint
67
68
  )
69
+ validate_status_code(response)
68
70
  return parse_json_response(response, "get secret with ID", secret_id=secret_id)
69
71
 
70
72
  def create_secret(
@@ -102,6 +104,7 @@ class SecretClient(BaseClient):
102
104
  endpoint=CREATE_SECRET_V1,
103
105
  data=data
104
106
  )
107
+ validate_status_code(response)
105
108
  return parse_json_response(response, "create secret with name", name=name)
106
109
 
107
110
  def update_secret(
@@ -143,6 +146,7 @@ class SecretClient(BaseClient):
143
146
  endpoint=endpoint,
144
147
  data=data
145
148
  )
149
+ validate_status_code(response)
146
150
  return parse_json_response(response, "update secret with ID", secret_id=secret_id)
147
151
 
148
152
  def set_secret_accesses(
@@ -183,6 +187,7 @@ class SecretClient(BaseClient):
183
187
  endpoint=endpoint,
184
188
  data=data
185
189
  )
190
+ validate_status_code(response)
186
191
  return parse_json_response(response, "set accesses for secret with ID", secret_id=secret_id)
187
192
 
188
193
  def get_secret_accesses(self, secret_id: str) -> dict:
@@ -203,4 +208,5 @@ class SecretClient(BaseClient):
203
208
  response = self.api_service.get(
204
209
  endpoint=endpoint
205
210
  )
211
+ validate_status_code(response)
206
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):
pygeai/dbg/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ from pygeai.dbg.debugger import Debugger, Breakpoint
2
+
3
+ __all__ = ['Debugger', 'Breakpoint']