pygeai 0.6.0b7__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.
- pygeai/_docs/source/conf.py +78 -6
- pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
- pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
- pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
- pygeai/_docs/source/content/api_reference/files.rst +592 -0
- pygeai/_docs/source/content/api_reference/gam.rst +401 -0
- pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
- pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
- pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
- pygeai/_docs/source/content/api_reference.rst +7 -0
- pygeai/_docs/source/content/debugger.rst +376 -83
- pygeai/_docs/source/content/migration.rst +528 -0
- pygeai/_docs/source/content/modules.rst +1 -1
- pygeai/_docs/source/pygeai.cli.rst +8 -0
- pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
- pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
- pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
- pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
- pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
- pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
- pygeai/admin/clients.py +5 -0
- pygeai/assistant/clients.py +7 -0
- pygeai/assistant/data_analyst/clients.py +2 -0
- pygeai/assistant/rag/clients.py +11 -0
- pygeai/chat/clients.py +191 -25
- pygeai/chat/endpoints.py +2 -1
- pygeai/cli/commands/chat.py +227 -1
- pygeai/cli/commands/embeddings.py +56 -8
- pygeai/cli/commands/migrate.py +994 -434
- pygeai/cli/error_handler.py +116 -0
- pygeai/cli/geai.py +28 -10
- pygeai/cli/parsers.py +8 -2
- pygeai/core/base/clients.py +3 -1
- pygeai/core/common/exceptions.py +11 -10
- pygeai/core/embeddings/__init__.py +19 -0
- pygeai/core/embeddings/clients.py +17 -2
- pygeai/core/embeddings/mappers.py +16 -2
- pygeai/core/embeddings/responses.py +9 -2
- pygeai/core/feedback/clients.py +1 -0
- pygeai/core/files/clients.py +5 -7
- pygeai/core/files/managers.py +42 -0
- pygeai/core/llm/clients.py +4 -0
- pygeai/core/plugins/clients.py +1 -0
- pygeai/core/rerank/clients.py +1 -0
- pygeai/core/secrets/clients.py +6 -0
- pygeai/core/services/rest.py +1 -1
- pygeai/dbg/__init__.py +3 -0
- pygeai/dbg/debugger.py +565 -70
- pygeai/evaluation/clients.py +1 -1
- pygeai/evaluation/dataset/clients.py +45 -44
- pygeai/evaluation/plan/clients.py +27 -26
- pygeai/evaluation/result/clients.py +37 -5
- pygeai/gam/clients.py +4 -0
- pygeai/health/clients.py +1 -0
- pygeai/lab/agents/clients.py +8 -1
- pygeai/lab/models.py +3 -3
- pygeai/lab/processes/clients.py +21 -0
- pygeai/lab/strategies/clients.py +4 -0
- pygeai/lab/tools/clients.py +1 -0
- pygeai/migration/__init__.py +31 -0
- pygeai/migration/strategies.py +404 -155
- pygeai/migration/tools.py +170 -3
- pygeai/organization/clients.py +13 -0
- pygeai/organization/limits/clients.py +15 -0
- pygeai/proxy/clients.py +3 -1
- pygeai/tests/admin/test_clients.py +16 -11
- pygeai/tests/assistants/rag/test_clients.py +35 -23
- pygeai/tests/assistants/test_clients.py +22 -15
- pygeai/tests/auth/test_clients.py +14 -6
- pygeai/tests/chat/test_clients.py +211 -1
- pygeai/tests/cli/commands/test_embeddings.py +32 -9
- pygeai/tests/cli/commands/test_evaluation.py +7 -0
- pygeai/tests/cli/commands/test_migrate.py +112 -243
- pygeai/tests/cli/test_error_handler.py +225 -0
- pygeai/tests/cli/test_geai_driver.py +154 -0
- pygeai/tests/cli/test_parsers.py +5 -5
- pygeai/tests/core/embeddings/test_clients.py +144 -0
- pygeai/tests/core/embeddings/test_managers.py +171 -0
- pygeai/tests/core/embeddings/test_mappers.py +142 -0
- pygeai/tests/core/feedback/test_clients.py +2 -0
- pygeai/tests/core/files/test_clients.py +1 -0
- pygeai/tests/core/llm/test_clients.py +14 -9
- pygeai/tests/core/plugins/test_clients.py +5 -3
- pygeai/tests/core/rerank/test_clients.py +1 -0
- pygeai/tests/core/secrets/test_clients.py +19 -13
- pygeai/tests/dbg/test_debugger.py +453 -75
- pygeai/tests/evaluation/dataset/test_clients.py +3 -1
- pygeai/tests/evaluation/plan/test_clients.py +4 -2
- pygeai/tests/evaluation/result/test_clients.py +7 -5
- pygeai/tests/gam/test_clients.py +1 -1
- pygeai/tests/health/test_clients.py +1 -0
- pygeai/tests/lab/agents/test_clients.py +9 -0
- pygeai/tests/lab/processes/test_clients.py +36 -0
- pygeai/tests/lab/processes/test_mappers.py +3 -0
- pygeai/tests/lab/strategies/test_clients.py +14 -9
- pygeai/tests/migration/test_strategies.py +45 -218
- pygeai/tests/migration/test_tools.py +133 -9
- pygeai/tests/organization/limits/test_clients.py +17 -0
- pygeai/tests/organization/test_clients.py +22 -0
- pygeai/tests/proxy/test_clients.py +2 -0
- pygeai/tests/proxy/test_integration.py +1 -0
- pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
- pygeai/tests/snippets/chat/get_response.py +15 -0
- pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
- pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
- pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
- pygeai/tests/snippets/dbg/__init__.py +0 -0
- pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
- pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
- pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
- pygeai/tests/snippets/dbg/stepping_example.py +40 -0
- pygeai/tests/snippets/embeddings/cache_example.py +31 -0
- pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
- pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
- pygeai/tests/snippets/embeddings/openai_example.py +30 -0
- pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
- pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
- pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
- pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
- pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
- pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
- pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
- pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
- pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
- pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
- pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
- pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
- pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
- pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
- pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
- pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
- pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
- pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
- pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
- pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
- pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
- pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
- pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
- pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
- pygeai/tests/snippets/migrate/__init__.py +45 -0
- pygeai/tests/snippets/migrate/agent_migration.py +110 -0
- pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
- pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
- pygeai/tests/snippets/migrate/process_migration.py +64 -0
- pygeai/tests/snippets/migrate/project_migration.py +42 -0
- pygeai/tests/snippets/migrate/tool_migration.py +64 -0
- pygeai/tests/snippets/organization/create_project.py +2 -2
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/METADATA +1 -1
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/RECORD +178 -96
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/WHEEL +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/entry_points.txt +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
message = ErrorHandler.handle_keyboard_interrupt()
|
|
93
|
+
Console.write_stdout(message)
|
|
94
|
+
return ExitCode.KEYBOARD_INTERRUPT
|
|
77
95
|
except Exception as e:
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
pygeai/core/base/clients.py
CHANGED
|
@@ -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 =
|
|
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
|
|
pygeai/core/common/exceptions.py
CHANGED
|
@@ -6,55 +6,56 @@ class GEAIException(Exception):
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class UnknownArgumentError(GEAIException):
|
|
9
|
-
"""
|
|
9
|
+
"""Raised when an unknown or invalid command/option is provided"""
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class MissingRequirementException(GEAIException):
|
|
14
|
-
"""
|
|
14
|
+
"""Raised when a required parameter or argument is missing"""
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class WrongArgumentError(GEAIException):
|
|
19
|
-
"""
|
|
19
|
+
"""Raised when arguments are incorrectly formatted or invalid"""
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class ServerResponseError(GEAIException):
|
|
24
|
-
"""
|
|
24
|
+
"""Raised when the server returns an error response"""
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class APIError(GEAIException):
|
|
29
|
-
"""
|
|
29
|
+
"""Raised when an API request fails"""
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class InvalidPathException(GEAIException):
|
|
34
|
-
"""
|
|
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
|
-
"""
|
|
39
|
+
"""Raised when JSON data cannot be parsed or is malformed"""
|
|
40
40
|
pass
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class InvalidAPIResponseException(GEAIException):
|
|
44
|
-
"""
|
|
44
|
+
"""Raised when the API response format is unexpected or invalid"""
|
|
45
45
|
pass
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class InvalidResponseException(GEAIException):
|
|
49
|
-
"""
|
|
49
|
+
"""Raised when a response cannot be retrieved or processed"""
|
|
50
50
|
pass
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class InvalidAgentException(GEAIException):
|
|
54
|
-
"""
|
|
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):
|
pygeai/core/feedback/clients.py
CHANGED
pygeai/core/files/clients.py
CHANGED
|
@@ -55,13 +55,8 @@ class FileClient(BaseClient):
|
|
|
55
55
|
headers=headers,
|
|
56
56
|
files=files
|
|
57
57
|
)
|
|
58
|
-
|
|
59
|
-
|
|
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)
|
pygeai/core/files/managers.py
CHANGED
|
@@ -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}")
|
pygeai/core/llm/clients.py
CHANGED
|
@@ -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}")
|
pygeai/core/plugins/clients.py
CHANGED
pygeai/core/rerank/clients.py
CHANGED
pygeai/core/secrets/clients.py
CHANGED
|
@@ -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)
|
pygeai/core/services/rest.py
CHANGED
|
@@ -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