pygeai 0.6.0b14__py3-none-any.whl → 0.6.1__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/__init__.py +1 -2
- pygeai/_docs/source/content/api_reference/project.rst +392 -0
- pygeai/_docs/source/content/authentication.rst +130 -1
- pygeai/_docs/source/content/debugger.rst +327 -157
- pygeai/_docs/source/pygeai.core.common.rst +8 -0
- pygeai/_docs/source/pygeai.tests.auth.rst +56 -0
- pygeai/_docs/source/pygeai.tests.cli.rst +8 -0
- pygeai/admin/clients.py +1 -3
- pygeai/analytics/clients.py +1 -1
- pygeai/assistant/clients.py +2 -7
- pygeai/assistant/data/clients.py +0 -8
- pygeai/assistant/data_analyst/clients.py +0 -2
- pygeai/assistant/managers.py +1 -1
- pygeai/assistant/rag/clients.py +0 -2
- pygeai/assistant/rag/mappers.py +9 -11
- pygeai/auth/clients.py +26 -7
- pygeai/auth/endpoints.py +2 -1
- pygeai/chat/clients.py +2 -2
- pygeai/chat/managers.py +1 -1
- pygeai/cli/commands/admin.py +13 -25
- pygeai/cli/commands/analytics.py +31 -71
- pygeai/cli/commands/assistant.py +84 -138
- pygeai/cli/commands/auth.py +23 -46
- pygeai/cli/commands/base.py +0 -1
- pygeai/cli/commands/chat.py +218 -209
- pygeai/cli/commands/common.py +5 -5
- pygeai/cli/commands/configuration.py +79 -29
- pygeai/cli/commands/docs.py +3 -4
- pygeai/cli/commands/embeddings.py +13 -19
- pygeai/cli/commands/evaluation.py +133 -344
- pygeai/cli/commands/feedback.py +7 -15
- pygeai/cli/commands/files.py +26 -53
- pygeai/cli/commands/gam.py +28 -69
- pygeai/cli/commands/lab/ai_lab.py +96 -142
- pygeai/cli/commands/lab/common.py +1 -1
- pygeai/cli/commands/lab/spec.py +12 -32
- pygeai/cli/commands/llm.py +9 -18
- pygeai/cli/commands/migrate.py +43 -99
- pygeai/cli/commands/organization.py +223 -196
- pygeai/cli/commands/rag.py +35 -58
- pygeai/cli/commands/rerank.py +21 -25
- pygeai/cli/commands/secrets.py +39 -67
- pygeai/cli/commands/usage_limits.py +50 -136
- pygeai/cli/commands/validators.py +1 -1
- pygeai/cli/geai.py +32 -3
- pygeai/cli/geai_proxy.py +6 -2
- pygeai/cli/install_man.py +1 -1
- pygeai/cli/parsers.py +1 -1
- pygeai/core/base/clients.py +90 -21
- pygeai/core/base/mappers.py +39 -55
- pygeai/core/base/session.py +129 -18
- pygeai/core/common/config.py +50 -13
- pygeai/core/common/constants.py +8 -0
- pygeai/core/common/exceptions.py +6 -0
- pygeai/core/embeddings/clients.py +0 -1
- pygeai/core/embeddings/managers.py +0 -1
- pygeai/core/feedback/clients.py +0 -2
- pygeai/core/feedback/models.py +1 -1
- pygeai/core/files/clients.py +0 -3
- pygeai/core/files/managers.py +1 -1
- pygeai/core/files/mappers.py +4 -5
- pygeai/core/llm/clients.py +0 -1
- pygeai/core/models.py +4 -4
- pygeai/core/plugins/clients.py +0 -3
- pygeai/core/plugins/models.py +2 -2
- pygeai/core/rerank/clients.py +0 -2
- pygeai/core/secrets/clients.py +0 -2
- pygeai/core/services/rest.py +80 -14
- pygeai/core/singleton.py +24 -0
- pygeai/dbg/__init__.py +2 -2
- pygeai/dbg/debugger.py +276 -38
- pygeai/evaluation/clients.py +2 -4
- pygeai/evaluation/dataset/clients.py +0 -1
- pygeai/evaluation/plan/clients.py +0 -2
- pygeai/evaluation/result/clients.py +0 -2
- pygeai/gam/clients.py +1 -3
- pygeai/health/clients.py +1 -3
- pygeai/lab/clients.py +0 -1
- pygeai/lab/managers.py +0 -1
- pygeai/lab/models.py +0 -1
- pygeai/lab/strategies/clients.py +1 -2
- pygeai/lab/tools/clients.py +2 -2
- pygeai/lab/tools/mappers.py +1 -1
- pygeai/migration/strategies.py +5 -6
- pygeai/migration/tools.py +1 -1
- pygeai/organization/clients.py +118 -12
- pygeai/organization/endpoints.py +1 -0
- pygeai/organization/limits/clients.py +4 -6
- pygeai/organization/limits/managers.py +1 -4
- pygeai/organization/managers.py +2 -2
- pygeai/proxy/config.py +1 -0
- pygeai/proxy/managers.py +6 -5
- pygeai/tests/admin/test_clients.py +11 -11
- pygeai/tests/assistants/rag/test_clients.py +1 -1
- pygeai/tests/assistants/rag/test_models.py +1 -2
- pygeai/tests/assistants/test_clients.py +1 -1
- pygeai/tests/assistants/test_managers.py +1 -3
- pygeai/tests/auth/test_cli_configuration.py +252 -0
- pygeai/tests/auth/test_client_initialization.py +411 -0
- pygeai/tests/auth/test_clients.py +29 -27
- pygeai/tests/auth/test_config_manager.py +305 -0
- pygeai/tests/auth/test_header_injection.py +294 -0
- pygeai/tests/auth/test_oauth.py +3 -1
- pygeai/tests/auth/test_session_logging.py +119 -0
- pygeai/tests/auth/test_session_validation.py +408 -0
- pygeai/tests/auth/test_singleton_reset.py +201 -0
- pygeai/tests/chat/test_clients.py +1 -1
- pygeai/tests/chat/test_iris.py +1 -1
- pygeai/tests/chat/test_ui.py +0 -2
- pygeai/tests/cli/commands/lab/test_ai_lab.py +1 -3
- pygeai/tests/cli/commands/lab/test_common.py +0 -1
- pygeai/tests/cli/commands/test_chat.py +1 -1
- pygeai/tests/cli/commands/test_common.py +0 -1
- pygeai/tests/cli/commands/test_embeddings.py +2 -2
- pygeai/tests/cli/commands/test_evaluation.py +1 -9
- pygeai/tests/cli/commands/test_llm.py +1 -1
- pygeai/tests/cli/commands/test_migrate.py +1 -1
- pygeai/tests/cli/commands/test_rerank.py +0 -1
- pygeai/tests/cli/commands/test_secrets.py +1 -1
- pygeai/tests/cli/commands/test_show_help.py +0 -1
- pygeai/tests/cli/commands/test_validators.py +0 -1
- pygeai/tests/cli/test_credentials_flag.py +312 -0
- pygeai/tests/cli/test_error_handler.py +0 -1
- pygeai/tests/core/base/test_mappers.py +2 -2
- pygeai/tests/core/base/test_models.py +4 -4
- pygeai/tests/core/common/test_config.py +2 -7
- pygeai/tests/core/common/test_decorators.py +0 -1
- pygeai/tests/core/embeddings/test_managers.py +1 -1
- pygeai/tests/core/feedback/test_clients.py +2 -2
- pygeai/tests/core/files/test_clients.py +6 -6
- pygeai/tests/core/files/test_models.py +0 -1
- pygeai/tests/core/files/test_responses.py +0 -1
- pygeai/tests/core/llm/test_clients.py +1 -1
- pygeai/tests/core/plugins/test_clients.py +4 -4
- pygeai/tests/core/rerank/test_mappers.py +1 -3
- pygeai/tests/core/secrets/test_clients.py +2 -3
- pygeai/tests/core/services/test_rest.py +10 -10
- pygeai/tests/core/utils/test_console.py +0 -1
- pygeai/tests/dbg/test_debugger.py +95 -8
- pygeai/tests/evaluation/dataset/test_clients.py +24 -27
- pygeai/tests/evaluation/plan/test_clients.py +16 -18
- pygeai/tests/evaluation/result/test_clients.py +4 -5
- pygeai/tests/health/test_clients.py +2 -2
- pygeai/tests/integration/lab/agents/test_create_agent.py +1 -3
- pygeai/tests/integration/lab/agents/test_get_agent.py +1 -1
- pygeai/tests/integration/lab/processes/test_create_process.py +2 -2
- pygeai/tests/integration/lab/processes/test_create_task.py +2 -3
- pygeai/tests/integration/lab/processes/test_delete_process.py +0 -1
- pygeai/tests/integration/lab/processes/test_get_process.py +2 -4
- pygeai/tests/integration/lab/processes/test_list_process_instances.py +1 -3
- pygeai/tests/integration/lab/processes/test_update_process.py +3 -9
- pygeai/tests/integration/lab/reasoning_strategies/test_update_reasoning_strategy.py +1 -2
- pygeai/tests/integration/lab/tools/test_delete_tool.py +1 -1
- pygeai/tests/integration/lab/tools/test_list_tools.py +1 -1
- pygeai/tests/integration/lab/tools/test_update_tool.py +1 -1
- pygeai/tests/lab/agents/test_clients.py +17 -17
- pygeai/tests/lab/processes/test_clients.py +67 -67
- pygeai/tests/lab/processes/test_mappers.py +23 -23
- pygeai/tests/lab/spec/test_loader.py +0 -2
- pygeai/tests/lab/spec/test_parsers.py +1 -2
- pygeai/tests/lab/strategies/test_clients.py +10 -10
- pygeai/tests/lab/test_managers.py +3 -5
- pygeai/tests/lab/test_mappers.py +1 -4
- pygeai/tests/lab/tools/test_clients.py +21 -21
- pygeai/tests/lab/tools/test_mappers.py +0 -1
- pygeai/tests/organization/limits/test_clients.py +33 -33
- pygeai/tests/organization/limits/test_managers.py +7 -7
- pygeai/tests/organization/test_clients.py +78 -60
- pygeai/tests/proxy/test_clients.py +1 -1
- pygeai/tests/proxy/test_integration.py +1 -4
- pygeai/tests/proxy/test_managers.py +1 -2
- pygeai/tests/proxy/test_servers.py +1 -2
- pygeai/tests/snippets/assistants/rag/delete_rag_assistant.py +0 -1
- pygeai/tests/snippets/assistants/rag/get_documents.py +0 -1
- pygeai/tests/snippets/assistants/rag/get_rag_assistant_data.py +0 -1
- pygeai/tests/snippets/chat/get_request_status.py +0 -1
- pygeai/tests/snippets/dbg/file_debugging.py +72 -0
- pygeai/tests/snippets/dbg/module_debugging.py +60 -0
- pygeai/tests/snippets/embeddings/cohere_example.py +2 -2
- pygeai/tests/snippets/embeddings/openai_base64_example.py +1 -1
- pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +8 -8
- pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +5 -5
- pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +3 -3
- pygeai/tests/snippets/lab/agentic_flow_example_1.py +1 -1
- pygeai/tests/snippets/lab/agentic_flow_example_2.py +3 -4
- pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +2 -2
- pygeai/tests/snippets/lab/agents/delete_agent.py +1 -2
- pygeai/tests/snippets/lab/agents/get_agent.py +1 -1
- pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +10 -10
- pygeai/tests/snippets/lab/agents/get_sharing_link.py +0 -1
- pygeai/tests/snippets/lab/agents/list_agents.py +1 -1
- pygeai/tests/snippets/lab/agents/publish_agent_revision.py +0 -1
- pygeai/tests/snippets/lab/agents/update_agent_properties.py +1 -1
- pygeai/tests/snippets/lab/crud_ui.py +3 -5
- pygeai/tests/snippets/lab/processes/kbs/get_kb.py +0 -1
- pygeai/tests/snippets/lab/processes/kbs/list_kbs.py +0 -1
- pygeai/tests/snippets/lab/processes/list_processes.py +1 -1
- pygeai/tests/snippets/lab/samples/summarize_files.py +0 -3
- pygeai/tests/snippets/lab/strategies/get_reasoning_strategy.py +0 -1
- pygeai/tests/snippets/lab/strategies/list_reasoning_strategies.py +1 -1
- pygeai/tests/snippets/lab/tools/get_tool.py +1 -1
- pygeai/tests/snippets/lab/tools/publish_tool_revision.py +0 -1
- pygeai/tests/snippets/lab/tools/set_parameters.py +1 -2
- pygeai/tests/snippets/lab/use_cases/c_code_fixer_agent_flow.py +2 -3
- pygeai/tests/snippets/lab/use_cases/file_summarizer_example_2.py +1 -1
- pygeai/tests/snippets/lab/use_cases/update_cli_expert.py +0 -1
- pygeai/tests/snippets/lab/use_cases/update_lab_expert.py +0 -1
- pygeai/tests/snippets/lab/use_cases/update_web_designer.py +0 -1
- pygeai/tests/snippets/lab/use_cases/update_web_reader.py +0 -1
- pygeai/tests/snippets/migrate/orchestrator_examples.py +1 -1
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/METADATA +32 -7
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/RECORD +216 -205
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/WHEEL +0 -0
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/entry_points.txt +0 -0
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/top_level.txt +0 -0
pygeai/core/base/session.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import warnings
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from pygeai import logger
|
|
5
6
|
from pygeai.core.common.config import get_settings
|
|
6
|
-
from pygeai.core.common.
|
|
7
|
+
from pygeai.core.common.constants import AuthType
|
|
8
|
+
from pygeai.core.common.exceptions import MissingRequirementException, MixedAuthenticationException
|
|
7
9
|
from pygeai.core.singleton import Singleton
|
|
8
10
|
|
|
9
|
-
settings = get_settings()
|
|
10
|
-
|
|
11
11
|
|
|
12
12
|
_session = None
|
|
13
13
|
|
|
@@ -15,14 +15,21 @@ _session = None
|
|
|
15
15
|
class Session(metaclass=Singleton):
|
|
16
16
|
"""
|
|
17
17
|
A session to store configuration state required to interact with different resources.
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
|
|
19
|
+
Authentication Methods:
|
|
20
|
+
- API Key: Use api_key parameter
|
|
21
|
+
- OAuth 2.0: Use access_token and project_id parameters
|
|
22
|
+
|
|
23
|
+
:param api_key: str - API key to interact with GEAI (mutually exclusive with access_token unless allow_mixed_auth=True)
|
|
20
24
|
:param base_url: str - Base URL of the GEAI instance
|
|
21
25
|
:param eval_url: Optional[str] - Optional evaluation endpoint URL
|
|
22
|
-
:param access_token: Optional[str] - OAuth access token (keyword-only)
|
|
23
|
-
:param project_id: Optional[str] - Project ID for OAuth authentication (keyword-only)
|
|
26
|
+
:param access_token: Optional[str] - OAuth 2.0 access token (keyword-only, requires project_id)
|
|
27
|
+
:param project_id: Optional[str] - Project ID for OAuth authentication (keyword-only, requires access_token)
|
|
28
|
+
:param organization_id: Optional[str] - Organization ID for OAuth authentication (keyword-only)
|
|
29
|
+
:param alias: Optional[str] - Alias name for this session
|
|
30
|
+
:param allow_mixed_auth: bool - If True, allow both api_key and access_token (access_token takes precedence). Default False.
|
|
24
31
|
:return: Session - Instance of the Session class
|
|
25
|
-
:raises: ValueError - If
|
|
32
|
+
:raises: ValueError - If authentication configuration is invalid
|
|
26
33
|
"""
|
|
27
34
|
|
|
28
35
|
def __init__(
|
|
@@ -33,10 +40,34 @@ class Session(metaclass=Singleton):
|
|
|
33
40
|
*,
|
|
34
41
|
access_token: Optional[str] = None,
|
|
35
42
|
project_id: Optional[str] = None,
|
|
43
|
+
organization_id: Optional[str] = None,
|
|
36
44
|
alias: Optional[str] = None,
|
|
45
|
+
allow_mixed_auth: bool = True,
|
|
37
46
|
):
|
|
47
|
+
# Validate authentication configuration
|
|
48
|
+
if api_key and access_token and not allow_mixed_auth:
|
|
49
|
+
raise MixedAuthenticationException(
|
|
50
|
+
"Cannot specify both 'api_key' and 'access_token'. "
|
|
51
|
+
"Use 'api_key' for API Key authentication or 'access_token' with 'project_id' for OAuth 2.0. "
|
|
52
|
+
"Set allow_mixed_auth=True to allow both (access_token will take precedence)."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if access_token and not project_id:
|
|
56
|
+
raise MissingRequirementException(
|
|
57
|
+
"OAuth 2.0 authentication requires 'project_id'. "
|
|
58
|
+
"Provide project_id when using access_token."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if project_id and not access_token:
|
|
62
|
+
warnings.warn(
|
|
63
|
+
"project_id provided without access_token. "
|
|
64
|
+
"project_id is only used with OAuth 2.0 authentication.",
|
|
65
|
+
UserWarning
|
|
66
|
+
)
|
|
67
|
+
|
|
38
68
|
if not api_key and not access_token:
|
|
39
|
-
logger.warning("
|
|
69
|
+
logger.warning("No authentication method configured. API calls may fail.")
|
|
70
|
+
|
|
40
71
|
if not base_url:
|
|
41
72
|
logger.warning("Cannot instantiate session without base_url")
|
|
42
73
|
|
|
@@ -45,18 +76,36 @@ class Session(metaclass=Singleton):
|
|
|
45
76
|
self.__eval_url = eval_url
|
|
46
77
|
self.__access_token = access_token
|
|
47
78
|
self.__project_id = project_id
|
|
79
|
+
self.__organization_id = organization_id
|
|
48
80
|
self.__alias = alias if alias else "default"
|
|
81
|
+
self.__auth_type = self._determine_auth_type()
|
|
49
82
|
|
|
50
83
|
global _session
|
|
51
84
|
_session = self
|
|
52
85
|
|
|
86
|
+
def _determine_auth_type(self) -> AuthType:
|
|
87
|
+
"""Determine the active authentication type based on configuration."""
|
|
88
|
+
if self.__access_token and self.__project_id:
|
|
89
|
+
return AuthType.OAUTH_TOKEN
|
|
90
|
+
elif self.__api_key:
|
|
91
|
+
return AuthType.API_KEY
|
|
92
|
+
else:
|
|
93
|
+
return AuthType.NONE
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def auth_type(self) -> AuthType:
|
|
97
|
+
"""Get the current authentication type."""
|
|
98
|
+
return self.__auth_type
|
|
99
|
+
|
|
53
100
|
@property
|
|
54
101
|
def api_key(self):
|
|
55
102
|
return self.__api_key
|
|
56
103
|
|
|
57
104
|
@api_key.setter
|
|
58
105
|
def api_key(self, api_key: str):
|
|
106
|
+
"""Set API key and update auth type."""
|
|
59
107
|
self.__api_key = api_key
|
|
108
|
+
self.__auth_type = self._determine_auth_type()
|
|
60
109
|
|
|
61
110
|
@property
|
|
62
111
|
def base_url(self):
|
|
@@ -80,7 +129,9 @@ class Session(metaclass=Singleton):
|
|
|
80
129
|
|
|
81
130
|
@access_token.setter
|
|
82
131
|
def access_token(self, access_token: str):
|
|
132
|
+
"""Set access token and update auth type."""
|
|
83
133
|
self.__access_token = access_token
|
|
134
|
+
self.__auth_type = self._determine_auth_type()
|
|
84
135
|
|
|
85
136
|
@property
|
|
86
137
|
def project_id(self):
|
|
@@ -88,7 +139,17 @@ class Session(metaclass=Singleton):
|
|
|
88
139
|
|
|
89
140
|
@project_id.setter
|
|
90
141
|
def project_id(self, project_id: str):
|
|
142
|
+
"""Set project ID and update auth type."""
|
|
91
143
|
self.__project_id = project_id
|
|
144
|
+
self.__auth_type = self._determine_auth_type()
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def organization_id(self):
|
|
148
|
+
return self.__organization_id
|
|
149
|
+
|
|
150
|
+
@organization_id.setter
|
|
151
|
+
def organization_id(self, organization_id: str):
|
|
152
|
+
self.__organization_id = organization_id
|
|
92
153
|
|
|
93
154
|
@property
|
|
94
155
|
def alias(self):
|
|
@@ -98,14 +159,33 @@ class Session(metaclass=Singleton):
|
|
|
98
159
|
def alias(self, alias: str):
|
|
99
160
|
self.__alias = alias
|
|
100
161
|
|
|
162
|
+
def is_oauth(self) -> bool:
|
|
163
|
+
"""Check if session is using OAuth authentication."""
|
|
164
|
+
return self.__auth_type == AuthType.OAUTH_TOKEN
|
|
165
|
+
|
|
166
|
+
def is_api_key(self) -> bool:
|
|
167
|
+
"""Check if session is using API key authentication."""
|
|
168
|
+
return self.__auth_type == AuthType.API_KEY
|
|
169
|
+
|
|
170
|
+
def get_active_token(self) -> Optional[str]:
|
|
171
|
+
"""Get the active authentication token based on auth type."""
|
|
172
|
+
if self.is_oauth():
|
|
173
|
+
return self.__access_token
|
|
174
|
+
elif self.is_api_key():
|
|
175
|
+
return self.__api_key
|
|
176
|
+
return None
|
|
177
|
+
|
|
101
178
|
|
|
102
179
|
def get_session(alias: str = None) -> Session:
|
|
103
180
|
"""
|
|
104
181
|
Session is a singleton object:
|
|
105
182
|
On the first invocation, returns Session configured with the API KEY and BASE URL corresponding to the
|
|
106
183
|
alias provided. On the following invocations, it returns the first object instantiated.
|
|
184
|
+
|
|
185
|
+
Loads both API Key and OAuth 2.0 credentials from configuration if available.
|
|
107
186
|
"""
|
|
108
187
|
try:
|
|
188
|
+
settings = get_settings()
|
|
109
189
|
global _session
|
|
110
190
|
if _session is None:
|
|
111
191
|
if not alias:
|
|
@@ -113,27 +193,43 @@ def get_session(alias: str = None) -> Session:
|
|
|
113
193
|
|
|
114
194
|
_validate_alias(alias, allow_missing_default=True)
|
|
115
195
|
|
|
196
|
+
api_key = settings.get_api_key(alias)
|
|
197
|
+
access_token = settings.get_access_token(alias)
|
|
198
|
+
|
|
199
|
+
# Allow mixed auth for backward compatibility
|
|
200
|
+
# If both are configured, allow mixed (with warning logged in Session)
|
|
201
|
+
allow_mixed = bool(api_key and access_token)
|
|
202
|
+
|
|
203
|
+
if allow_mixed:
|
|
204
|
+
logger.warning(
|
|
205
|
+
f"Both API key and OAuth token configured for alias '{alias}'. "
|
|
206
|
+
"OAuth token will take precedence. Consider using separate aliases."
|
|
207
|
+
)
|
|
208
|
+
|
|
116
209
|
_session = Session(
|
|
117
|
-
api_key=
|
|
210
|
+
api_key=api_key,
|
|
118
211
|
base_url=settings.get_base_url(alias),
|
|
119
212
|
eval_url=settings.get_eval_url(alias),
|
|
120
|
-
access_token=
|
|
213
|
+
access_token=access_token,
|
|
121
214
|
project_id=settings.get_project_id(alias),
|
|
215
|
+
organization_id=settings.get_organization_id(alias),
|
|
122
216
|
alias=alias,
|
|
217
|
+
allow_mixed_auth=allow_mixed,
|
|
123
218
|
)
|
|
219
|
+
|
|
124
220
|
elif _session is not None and alias:
|
|
125
221
|
_validate_alias(alias, allow_missing_default=False)
|
|
126
222
|
|
|
223
|
+
api_key = settings.get_api_key(alias)
|
|
224
|
+
access_token = settings.get_access_token(alias)
|
|
225
|
+
|
|
127
226
|
_session.alias = alias
|
|
128
|
-
_session.api_key =
|
|
227
|
+
_session.api_key = api_key
|
|
129
228
|
_session.base_url = settings.get_base_url(alias)
|
|
130
229
|
_session.eval_url = settings.get_eval_url(alias)
|
|
131
|
-
_session.access_token =
|
|
230
|
+
_session.access_token = access_token
|
|
132
231
|
_session.project_id = settings.get_project_id(alias)
|
|
133
|
-
|
|
134
|
-
if alias:
|
|
135
|
-
logger.debug(f"Alias: {alias}")
|
|
136
|
-
logger.debug(f"Base URL: {_session.base_url}")
|
|
232
|
+
_session.organization_id = settings.get_organization_id(alias)
|
|
137
233
|
|
|
138
234
|
return _session
|
|
139
235
|
except ValueError as e:
|
|
@@ -142,10 +238,25 @@ def get_session(alias: str = None) -> Session:
|
|
|
142
238
|
|
|
143
239
|
|
|
144
240
|
def _validate_alias(alias: str, allow_missing_default: bool = False):
|
|
241
|
+
settings = get_settings()
|
|
145
242
|
available_aliases = settings.list_aliases()
|
|
146
243
|
if alias not in available_aliases:
|
|
147
244
|
if allow_missing_default and alias == "default":
|
|
148
245
|
return
|
|
149
246
|
raise MissingRequirementException(
|
|
150
247
|
f"The profile '{alias}' doesn't exist. Use 'geai configure --list' to see available profiles."
|
|
151
|
-
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def reset_session():
|
|
252
|
+
"""
|
|
253
|
+
Reset the session instance. Useful for testing.
|
|
254
|
+
|
|
255
|
+
This clears both the module-level _session variable and the
|
|
256
|
+
Singleton metaclass cache to ensure proper test isolation.
|
|
257
|
+
"""
|
|
258
|
+
global _session
|
|
259
|
+
_session = None
|
|
260
|
+
|
|
261
|
+
from pygeai.core.singleton import Singleton
|
|
262
|
+
Singleton.reset_instance(Session)
|
pygeai/core/common/config.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
-
from functools import lru_cache
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
import configparser
|
|
6
5
|
|
|
@@ -21,9 +20,13 @@ class SettingsManager:
|
|
|
21
20
|
GEAI_SETTINGS_DIR = str(SETTINGS_DIR)
|
|
22
21
|
GEAI_CREDS_FILE = SETTINGS_DIR / "credentials"
|
|
23
22
|
|
|
24
|
-
def __init__(self):
|
|
23
|
+
def __init__(self, credentials_file: str = None):
|
|
25
24
|
self.config = configparser.ConfigParser()
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
if credentials_file:
|
|
27
|
+
self.GEAI_CREDS_FILE = Path(credentials_file)
|
|
28
|
+
logger.debug(f"Using custom credentials file: {self.GEAI_CREDS_FILE}")
|
|
29
|
+
|
|
27
30
|
if self.GEAI_CREDS_FILE.exists():
|
|
28
31
|
self.config.read(self.GEAI_CREDS_FILE)
|
|
29
32
|
else:
|
|
@@ -41,14 +44,19 @@ class SettingsManager:
|
|
|
41
44
|
return
|
|
42
45
|
|
|
43
46
|
if setting_key not in self.config[alias]:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if setting_key.lower() == "
|
|
47
|
+
logger.debug(f"'{setting_key}' not found in alias '{alias}' in the credentials file.\n")
|
|
48
|
+
|
|
49
|
+
if setting_key.lower() == "geai_api_eval_url" or ("geai_oauth_access_token" in self.config[alias] and setting_key.lower() in ["geai_project_id", "geai_organization_id"]):
|
|
50
|
+
sys.stdout.write(f"'{setting_key}' not found in alias '{alias}' in the credentials file. Adding empty value\n")
|
|
51
|
+
|
|
52
|
+
if setting_key.lower() == "geai_api_eval_url":
|
|
47
53
|
self.set_eval_url("", alias)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
|
|
55
|
+
if "geai_oauth_access_token" in self.config[alias]:
|
|
56
|
+
if setting_key.lower() == "geai_project_id":
|
|
57
|
+
self.set_project_id("", alias)
|
|
58
|
+
if setting_key.lower() == "geai_organization_id":
|
|
59
|
+
self.set_organization_id("", alias)
|
|
52
60
|
|
|
53
61
|
return self.config[alias].get(setting_key, "")
|
|
54
62
|
|
|
@@ -112,6 +120,16 @@ class SettingsManager:
|
|
|
112
120
|
def set_eval_url(self, eval_url, alias: str = "default"):
|
|
113
121
|
self.set_setting_value("GEAI_API_EVAL_URL", eval_url, alias)
|
|
114
122
|
|
|
123
|
+
def get_organization_id(self, alias: str = "default"):
|
|
124
|
+
organization_id = os.environ.get("GEAI_ORGANIZATION_ID") if not alias or alias == "default" else None
|
|
125
|
+
if not organization_id:
|
|
126
|
+
organization_id = self.get_setting_value("GEAI_ORGANIZATION_ID", alias)
|
|
127
|
+
|
|
128
|
+
return organization_id
|
|
129
|
+
|
|
130
|
+
def set_organization_id(self, organization_id, alias: str = "default"):
|
|
131
|
+
self.set_setting_value("GEAI_ORGANIZATION_ID", organization_id, alias)
|
|
132
|
+
|
|
115
133
|
def list_aliases(self):
|
|
116
134
|
"""Returns a dict of all aliases and their base URLs."""
|
|
117
135
|
return {
|
|
@@ -130,9 +148,28 @@ class SettingsManager:
|
|
|
130
148
|
logger.warning(f"Alias '{alias}' not found in the credentials file.")
|
|
131
149
|
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
_settings_instance = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_settings(credentials_file: str = None):
|
|
155
|
+
"""
|
|
156
|
+
Get or create the SettingsManager instance.
|
|
157
|
+
|
|
158
|
+
:param credentials_file: Optional path to a custom credentials file.
|
|
159
|
+
If provided on first call, uses that file.
|
|
160
|
+
Subsequent calls ignore this parameter.
|
|
161
|
+
:return: SettingsManager instance
|
|
162
|
+
"""
|
|
163
|
+
global _settings_instance
|
|
164
|
+
if _settings_instance is None:
|
|
165
|
+
_settings_instance = SettingsManager(credentials_file=credentials_file)
|
|
166
|
+
return _settings_instance
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def reset_settings():
|
|
170
|
+
"""Reset the settings instance. Useful for testing."""
|
|
171
|
+
global _settings_instance
|
|
172
|
+
_settings_instance = None
|
|
136
173
|
|
|
137
174
|
|
|
138
175
|
if __name__ == "__main__":
|
pygeai/core/common/exceptions.py
CHANGED
|
@@ -122,3 +122,9 @@ class APIResponseError(GEAIException):
|
|
|
122
122
|
"""Raised when there is an error in the API response"""
|
|
123
123
|
pass
|
|
124
124
|
|
|
125
|
+
|
|
126
|
+
class MixedAuthenticationException(GEAIException):
|
|
127
|
+
"""Raised when both API token and Oauth2 authentication are setup for the same profile"""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
|
|
@@ -1,7 +1,6 @@
|
|
|
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, APIResponseError
|
|
5
4
|
from pygeai.core.embeddings.endpoints import GENERATE_EMBEDDINGS
|
|
6
5
|
from pygeai.core.utils.validators import validate_status_code
|
|
7
6
|
from pygeai.core.utils.parsers import parse_json_response
|
pygeai/core/feedback/clients.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from typing import Any, Union
|
|
2
1
|
|
|
3
2
|
from pygeai import logger
|
|
4
3
|
from pygeai.core.base.clients import BaseClient
|
|
5
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
6
4
|
from pygeai.core.feedback.endpoints import SEND_FEEDBACK_V1
|
|
7
5
|
from pygeai.core.utils.validators import validate_status_code
|
|
8
6
|
from pygeai.core.utils.parsers import parse_json_response
|
pygeai/core/feedback/models.py
CHANGED
pygeai/core/files/clients.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from json import JSONDecodeError
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
|
|
5
3
|
from pygeai import logger
|
|
6
4
|
from pygeai.core.base.clients import BaseClient
|
|
7
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
8
5
|
from pygeai.core.files.endpoints import UPLOAD_FILE_V1, GET_FILE_V1, DELETE_FILE_V1, GET_FILE_CONTENT_V1, \
|
|
9
6
|
GET_ALL_FILES_V1
|
|
10
7
|
from pygeai.core.utils.validators import validate_status_code
|
pygeai/core/files/managers.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
from pygeai import logger
|
|
6
6
|
from pygeai.admin.clients import AdminClient
|
|
7
|
-
from pygeai.core.base.mappers import
|
|
7
|
+
from pygeai.core.base.mappers import ResponseMapper
|
|
8
8
|
from pygeai.core.base.responses import EmptyResponse
|
|
9
9
|
from pygeai.core.files.clients import FileClient
|
|
10
10
|
from pygeai.core.files.models import UploadFile, File, FileList
|
pygeai/core/files/mappers.py
CHANGED
|
@@ -22,14 +22,13 @@ class FileResponseMapper:
|
|
|
22
22
|
|
|
23
23
|
@classmethod
|
|
24
24
|
def map_to_file_list(cls, data: dict) -> list[File]:
|
|
25
|
-
file_list = list()
|
|
26
25
|
files = data.get('dataFiles')
|
|
26
|
+
|
|
27
27
|
if files is not None and any(files):
|
|
28
|
-
for file_data in files:
|
|
29
|
-
file = cls.map_to_file(file_data)
|
|
30
|
-
file_list.append(file)
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
return [cls.map_to_file(file_data) for file_data in files]
|
|
30
|
+
|
|
31
|
+
return []
|
|
33
32
|
|
|
34
33
|
@classmethod
|
|
35
34
|
def map_to_file(cls, data: dict) -> File:
|
pygeai/core/llm/clients.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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
|
|
5
4
|
from pygeai.core.llm.endpoints import GET_PROVIDER_LIST_V2, GET_PROVIDER_DATA_V2, GET_PROVIDER_MODELS_V2, \
|
|
6
5
|
GET_MODEL_DATA_V2
|
|
7
6
|
from pygeai.core.utils.validators import validate_status_code
|
pygeai/core/models.py
CHANGED
|
@@ -185,8 +185,8 @@ class UsageLimit(CustomBaseModel):
|
|
|
185
185
|
"subscriptionType": "string", // Subscription type (Freemium, Daily, Weekly, Monthly)
|
|
186
186
|
"usageUnit": "string", // Usage unit (Requests, Cost)
|
|
187
187
|
"usedAmount": "number", // Amount used (decimal or scientific notation)
|
|
188
|
-
"validFrom": "
|
|
189
|
-
"validUntil": "
|
|
188
|
+
"validFrom": "str", // Start date of the usage limit
|
|
189
|
+
"validUntil": "str" // Expiration or renewal date
|
|
190
190
|
"""
|
|
191
191
|
hard_limit: Optional[float] = Field(None, alias="hardLimit")
|
|
192
192
|
id: Optional[str] = Field(None, alias="id")
|
|
@@ -198,8 +198,8 @@ class UsageLimit(CustomBaseModel):
|
|
|
198
198
|
subscription_type: Optional[Literal["Freemium", "Daily", "Weekly", "Monthly"]] = Field(None, alias="subscriptionType")
|
|
199
199
|
usage_unit: Optional[Literal["Requests", "Cost"]] = Field(None, alias="usageUnit")
|
|
200
200
|
used_amount: Optional[float] = Field(None, alias="usedAmount")
|
|
201
|
-
valid_from: Optional[
|
|
202
|
-
valid_until: Optional[
|
|
201
|
+
valid_from: Optional[str] = Field(None, alias="validFrom")
|
|
202
|
+
valid_until: Optional[str] = Field(None, alias="validUntil")
|
|
203
203
|
|
|
204
204
|
def to_dict(self):
|
|
205
205
|
return self.model_dump(by_alias=True, exclude_none=True)
|
pygeai/core/plugins/clients.py
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
from typing import Optional, List, Dict
|
|
2
1
|
|
|
3
|
-
from pygeai import logger
|
|
4
2
|
from pygeai.core.base.clients import BaseClient
|
|
5
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
6
3
|
from pygeai.core.plugins.endpoints import LIST_ASSISTANTS_PLUGINS_V1
|
|
7
4
|
from pygeai.core.utils.validators import validate_status_code
|
|
8
5
|
from pygeai.core.utils.parsers import parse_json_response
|
pygeai/core/plugins/models.py
CHANGED
pygeai/core/rerank/clients.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from typing import Any, Union
|
|
2
1
|
|
|
3
2
|
from pygeai import logger
|
|
4
3
|
from pygeai.core.base.clients import BaseClient
|
|
5
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
6
4
|
from pygeai.core.rerank.endpoints import RERANK_V1
|
|
7
5
|
from pygeai.core.utils.validators import validate_status_code
|
|
8
6
|
from pygeai.core.utils.parsers import parse_json_response
|
pygeai/core/secrets/clients.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from typing import Optional, List, Dict
|
|
2
2
|
|
|
3
|
-
from pygeai import logger
|
|
4
3
|
from pygeai.core.base.clients import BaseClient
|
|
5
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
6
4
|
from pygeai.core.secrets.endpoints import LIST_SECRETS_V1, GET_SECRET_V1, CREATE_SECRET_V1, UPDATE_SECRET_V1, \
|
|
7
5
|
SET_SECRET_ACCESSES_V1, GET_SECRET_ACCESSES_V1
|
|
8
6
|
from pygeai.core.utils.validators import validate_status_code
|
pygeai/core/services/rest.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Optional,
|
|
2
|
+
from typing import Optional, Dict
|
|
3
3
|
import requests as req
|
|
4
4
|
from pygeai import logger
|
|
5
5
|
from pygeai.core.common.exceptions import InvalidResponseException
|
|
@@ -21,16 +21,13 @@ class ApiService:
|
|
|
21
21
|
:param username: str - Username for basic authentication (optional).
|
|
22
22
|
:param password: str - Password for basic authentication (optional).
|
|
23
23
|
:param token: str - Bearer token for authentication (optional).
|
|
24
|
-
:param project_id: str - Project ID for OAuth authentication (optional, keyword-only).
|
|
25
24
|
"""
|
|
26
25
|
|
|
27
|
-
def __init__(self, base_url, username: str = None, password: str = None, token: str = None
|
|
28
|
-
project_id: str = None):
|
|
26
|
+
def __init__(self, base_url, username: str = None, password: str = None, token: str = None):
|
|
29
27
|
self._base_url = base_url
|
|
30
28
|
self._username = username
|
|
31
29
|
self._password = password
|
|
32
30
|
self._token = token
|
|
33
|
-
self._project_id = project_id
|
|
34
31
|
|
|
35
32
|
@property
|
|
36
33
|
def base_url(self):
|
|
@@ -52,14 +49,6 @@ class ApiService:
|
|
|
52
49
|
def token(self, token: str):
|
|
53
50
|
self._token = token
|
|
54
51
|
|
|
55
|
-
@property
|
|
56
|
-
def project_id(self):
|
|
57
|
-
return self._project_id
|
|
58
|
-
|
|
59
|
-
@project_id.setter
|
|
60
|
-
def project_id(self, project_id: str):
|
|
61
|
-
self._project_id = project_id
|
|
62
|
-
|
|
63
52
|
def get(self, endpoint: str, params: dict = None, headers: dict = None, verify: bool = True):
|
|
64
53
|
"""
|
|
65
54
|
Sends a GET request to the specified API endpoint.
|
|
@@ -418,7 +407,84 @@ class ApiService:
|
|
|
418
407
|
if "Authorization" not in headers:
|
|
419
408
|
headers["Authorization"] = f"Bearer {self.token}"
|
|
420
409
|
|
|
421
|
-
|
|
410
|
+
return headers
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class GEAIApiService(ApiService):
|
|
414
|
+
"""
|
|
415
|
+
Service for interacting with REST APIs in Globant Enterprise AI.
|
|
416
|
+
|
|
417
|
+
:param base_url: str - The base URL of the API.
|
|
418
|
+
:param username: str - Username for basic authentication (optional).
|
|
419
|
+
:param password: str - Password for basic authentication (optional).
|
|
420
|
+
:param token: str - Bearer token for authentication (optional).
|
|
421
|
+
:param project_id: str - Project ID for OAuth authentication (optional, keyword-only).
|
|
422
|
+
:param organization_id: str - Organization ID for OAuth authentication (optional, keyword-only).
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
def __init__(self, base_url, username: str = None, password: str = None, token: str = None, *,
|
|
426
|
+
project_id: str = None, organization_id: str = None):
|
|
427
|
+
super().__init__(base_url, username, password, token)
|
|
428
|
+
self._project_id = project_id
|
|
429
|
+
self._organization_id = organization_id
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def project_id(self):
|
|
433
|
+
return self._project_id
|
|
434
|
+
|
|
435
|
+
@project_id.setter
|
|
436
|
+
def project_id(self, project_id: str):
|
|
437
|
+
self._project_id = project_id
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def organization_id(self):
|
|
441
|
+
return self._organization_id
|
|
442
|
+
|
|
443
|
+
@organization_id.setter
|
|
444
|
+
def organization_id(self, organization_id: str):
|
|
445
|
+
self._organization_id = organization_id
|
|
446
|
+
|
|
447
|
+
def get(self, endpoint: str, params: dict = None, headers: dict = None, verify: bool = True):
|
|
448
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
449
|
+
return super().get(endpoint, params, headers, verify)
|
|
450
|
+
|
|
451
|
+
def post(self, endpoint: str, data: dict, headers: dict = None, verify: bool = True, form: bool = False):
|
|
452
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
453
|
+
return super().post(endpoint, data, headers, verify, form)
|
|
454
|
+
|
|
455
|
+
def stream_post(self, endpoint: str, data: dict, headers: dict = None, verify: bool = True, form: bool = False):
|
|
456
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
457
|
+
return super().stream_post(endpoint, data, headers, verify, form)
|
|
458
|
+
|
|
459
|
+
def post_file_binary( self, endpoint: str, headers: dict = None, verify: bool = True, file=None):
|
|
460
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
461
|
+
return super().post_file_binary(endpoint, headers, verify, file)
|
|
462
|
+
|
|
463
|
+
def post_files_multipart(self, endpoint: str, data: Optional[dict] = None, headers: Optional[dict] = None, verify: bool = True, files: Optional[Dict[str, str]] = None):
|
|
464
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
465
|
+
return super().post_files_multipart(endpoint, data, headers, verify, files)
|
|
466
|
+
|
|
467
|
+
def put(self, endpoint: str, data: dict, headers: dict = None, verify: bool = True):
|
|
468
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
469
|
+
return super().put(endpoint, data, headers, verify)
|
|
470
|
+
|
|
471
|
+
def delete(self, endpoint: str, headers: dict = None, data: dict = None, verify: bool = True):
|
|
472
|
+
headers = self._add_oauth_context_to_headers(headers=headers)
|
|
473
|
+
return super().delete(endpoint, headers, data, verify)
|
|
474
|
+
|
|
475
|
+
def _add_oauth_context_to_headers(self, headers: dict = None):
|
|
476
|
+
headers = headers if headers is not None else {}
|
|
477
|
+
|
|
478
|
+
# Add OAuth context headers if available
|
|
479
|
+
if self.project_id and "ProjectId" not in headers and "project-id" not in headers:
|
|
422
480
|
headers["ProjectId"] = self.project_id
|
|
481
|
+
logger.debug(f"Added Authorization header with ProjectId: {self.project_id}")
|
|
482
|
+
|
|
483
|
+
if self.organization_id and "OrganizationId" not in headers and "organization-id" not in headers:
|
|
484
|
+
headers["OrganizationId"] = self.organization_id
|
|
485
|
+
logger.debug(f"Added OrganizationId header: {self.organization_id}")
|
|
486
|
+
|
|
487
|
+
if not self.project_id and not self.organization_id:
|
|
488
|
+
logger.debug("Added Authorization header")
|
|
423
489
|
|
|
424
490
|
return headers
|