mito-ai 0.1.57__py3-none-any.whl → 0.1.59__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.
- mito_ai/__init__.py +19 -22
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +24 -14
- mito_ai/chart_wizard/handlers.py +78 -17
- mito_ai/chart_wizard/urls.py +8 -5
- mito_ai/completions/completion_handlers/agent_auto_error_fixup_handler.py +6 -8
- mito_ai/completions/completion_handlers/agent_execution_handler.py +6 -8
- mito_ai/completions/completion_handlers/chat_completion_handler.py +13 -17
- mito_ai/completions/completion_handlers/code_explain_handler.py +13 -17
- mito_ai/completions/completion_handlers/completion_handler.py +3 -5
- mito_ai/completions/completion_handlers/inline_completer_handler.py +5 -6
- mito_ai/completions/completion_handlers/scratchpad_result_handler.py +6 -8
- mito_ai/completions/completion_handlers/smart_debug_handler.py +13 -17
- mito_ai/completions/completion_handlers/utils.py +3 -7
- mito_ai/completions/handlers.py +32 -22
- mito_ai/completions/message_history.py +8 -10
- mito_ai/completions/prompt_builders/chart_add_field_prompt.py +35 -0
- mito_ai/completions/prompt_builders/prompt_constants.py +2 -0
- mito_ai/constants.py +31 -2
- mito_ai/enterprise/__init__.py +1 -1
- mito_ai/enterprise/litellm_client.py +144 -0
- mito_ai/enterprise/utils.py +16 -2
- mito_ai/log/handlers.py +1 -1
- mito_ai/openai_client.py +36 -96
- mito_ai/provider_manager.py +420 -0
- mito_ai/settings/enterprise_handler.py +26 -0
- mito_ai/settings/urls.py +2 -0
- mito_ai/streamlit_conversion/agent_utils.py +2 -30
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +48 -46
- mito_ai/streamlit_preview/handlers.py +6 -3
- mito_ai/streamlit_preview/urls.py +5 -3
- mito_ai/tests/message_history/test_generate_short_chat_name.py +103 -28
- mito_ai/tests/open_ai_utils_test.py +34 -36
- mito_ai/tests/providers/test_anthropic_client.py +174 -16
- mito_ai/tests/providers/test_azure.py +15 -15
- mito_ai/tests/providers/test_capabilities.py +14 -17
- mito_ai/tests/providers/test_gemini_client.py +14 -13
- mito_ai/tests/providers/test_model_resolution.py +145 -89
- mito_ai/tests/providers/test_openai_client.py +209 -13
- mito_ai/tests/providers/test_provider_limits.py +5 -5
- mito_ai/tests/providers/test_providers.py +229 -51
- mito_ai/tests/providers/test_retry_logic.py +13 -22
- mito_ai/tests/providers/utils.py +4 -4
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +57 -85
- mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +4 -1
- mito_ai/tests/test_constants.py +90 -0
- mito_ai/tests/test_enterprise_mode.py +217 -0
- mito_ai/tests/test_model_utils.py +362 -0
- mito_ai/utils/anthropic_utils.py +8 -6
- mito_ai/utils/gemini_utils.py +0 -3
- mito_ai/utils/litellm_utils.py +84 -0
- mito_ai/utils/model_utils.py +257 -0
- mito_ai/utils/open_ai_utils.py +29 -41
- mito_ai/utils/provider_utils.py +13 -29
- mito_ai/utils/telemetry_utils.py +14 -2
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +102 -102
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.9d26322f3e78beb2b666.js → mito_ai-0.1.59.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.44c109c7be36fb884d25.js +1059 -144
- mito_ai-0.1.59.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.44c109c7be36fb884d25.js.map +1 -0
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.79c1ea8a3cda73a4cb6f.js → mito_ai-0.1.59.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.f7decebaf69618541e0f.js +17 -17
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.79c1ea8a3cda73a4cb6f.js.map → mito_ai-0.1.59.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.f7decebaf69618541e0f.js.map +1 -1
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/themes/mito_ai/index.css +78 -78
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.59.dist-info}/METADATA +2 -1
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.59.dist-info}/RECORD +90 -83
- mito_ai/completions/providers.py +0 -284
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.9d26322f3e78beb2b666.js.map +0 -1
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.f5d476ac514294615881.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.f5d476ac514294615881.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.59.data}/data/share/jupyter/labextensions/mito_ai/themes/mito_ai/index.js +0 -0
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.59.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.59.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.59.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the The Mito Enterprise license.
|
|
3
|
+
|
|
4
|
+
from typing import Optional, List, Callable, Union, Dict, Any
|
|
5
|
+
from openai.types.chat import ChatCompletionMessageParam
|
|
6
|
+
from mito_ai.completions.models import (
|
|
7
|
+
MessageType,
|
|
8
|
+
ResponseFormatInfo,
|
|
9
|
+
CompletionReply,
|
|
10
|
+
CompletionStreamChunk,
|
|
11
|
+
CompletionItem,
|
|
12
|
+
)
|
|
13
|
+
from mito_ai.utils.litellm_utils import get_litellm_completion_function_params
|
|
14
|
+
from mito_ai.utils.model_utils import strip_router_prefix
|
|
15
|
+
import litellm
|
|
16
|
+
|
|
17
|
+
class LiteLLMClient:
|
|
18
|
+
"""
|
|
19
|
+
A client for interacting with LiteLLM server endpoints.
|
|
20
|
+
LiteLLM provides an OpenAI-compatible API, so we use the LiteLLM SDK directly.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, api_key: Optional[str], base_url: str, timeout: int = 30, max_retries: int = 1):
|
|
24
|
+
self.api_key = api_key
|
|
25
|
+
self.base_url = base_url
|
|
26
|
+
self.timeout = timeout
|
|
27
|
+
self.max_retries = max_retries
|
|
28
|
+
|
|
29
|
+
async def request_completions(
|
|
30
|
+
self,
|
|
31
|
+
messages: List[ChatCompletionMessageParam],
|
|
32
|
+
model: str, # Should include provider prefix (e.g., "LiteLLM/openai/gpt-4o")
|
|
33
|
+
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
34
|
+
message_type: MessageType = MessageType.CHAT
|
|
35
|
+
) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Request completions from LiteLLM server.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
messages: List of chat messages
|
|
41
|
+
model: Model name with router and provider prefix (e.g., "LiteLLM/openai/gpt-4o")
|
|
42
|
+
response_format_info: Optional response format specification
|
|
43
|
+
message_type: Type of message (chat, agent execution, etc.)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The completion text response
|
|
47
|
+
"""
|
|
48
|
+
# Strip router prefix if present (LiteLLM/ prefix)
|
|
49
|
+
model_for_litellm = strip_router_prefix(model)
|
|
50
|
+
|
|
51
|
+
# Prepare parameters for LiteLLM
|
|
52
|
+
params = get_litellm_completion_function_params(
|
|
53
|
+
model=model_for_litellm,
|
|
54
|
+
messages=messages,
|
|
55
|
+
api_key=self.api_key,
|
|
56
|
+
api_base=self.base_url,
|
|
57
|
+
timeout=self.timeout,
|
|
58
|
+
stream=False,
|
|
59
|
+
response_format_info=response_format_info,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Use LiteLLM's acompletion function
|
|
64
|
+
response = await litellm.acompletion(**params)
|
|
65
|
+
|
|
66
|
+
# Extract content from response
|
|
67
|
+
if response and response.choices and len(response.choices) > 0:
|
|
68
|
+
content = response.choices[0].message.content
|
|
69
|
+
return content or ""
|
|
70
|
+
else:
|
|
71
|
+
return ""
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise Exception(f"LiteLLM completion error: {str(e)}")
|
|
74
|
+
|
|
75
|
+
async def stream_completions(
|
|
76
|
+
self,
|
|
77
|
+
messages: List[ChatCompletionMessageParam],
|
|
78
|
+
model: str,
|
|
79
|
+
message_type: MessageType,
|
|
80
|
+
message_id: str,
|
|
81
|
+
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None],
|
|
82
|
+
response_format_info: Optional[ResponseFormatInfo] = None
|
|
83
|
+
) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Stream completions from LiteLLM server.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
messages: List of chat messages
|
|
89
|
+
model: Model name with router and provider prefix (e.g., "LiteLLM/openai/gpt-4o")
|
|
90
|
+
message_type: Type of message (chat, agent execution, etc.)
|
|
91
|
+
message_id: ID of the message being processed
|
|
92
|
+
reply_fn: Function to call with each chunk for streaming replies
|
|
93
|
+
response_format_info: Optional response format specification
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The accumulated response string
|
|
97
|
+
"""
|
|
98
|
+
accumulated_response = ""
|
|
99
|
+
|
|
100
|
+
# Strip router prefix if present (LiteLLM/ prefix)
|
|
101
|
+
model_for_litellm = strip_router_prefix(model)
|
|
102
|
+
|
|
103
|
+
# Prepare parameters for LiteLLM
|
|
104
|
+
params = get_litellm_completion_function_params(
|
|
105
|
+
model=model_for_litellm,
|
|
106
|
+
messages=messages,
|
|
107
|
+
api_key=self.api_key,
|
|
108
|
+
api_base=self.base_url,
|
|
109
|
+
timeout=self.timeout,
|
|
110
|
+
stream=True,
|
|
111
|
+
response_format_info=response_format_info,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
# Use LiteLLM's acompletion with stream=True
|
|
116
|
+
# When stream=True, acompletion returns an async iterable after awaiting
|
|
117
|
+
stream = await litellm.acompletion(**params)
|
|
118
|
+
|
|
119
|
+
# Process streaming chunks
|
|
120
|
+
async for chunk in stream:
|
|
121
|
+
if chunk and chunk.choices and len(chunk.choices) > 0:
|
|
122
|
+
delta = chunk.choices[0].delta
|
|
123
|
+
content = delta.content if delta and delta.content else ""
|
|
124
|
+
|
|
125
|
+
if content:
|
|
126
|
+
accumulated_response += content
|
|
127
|
+
|
|
128
|
+
# Check if this is the final chunk
|
|
129
|
+
is_finished = chunk.choices[0].finish_reason is not None
|
|
130
|
+
|
|
131
|
+
# Send chunk to frontend
|
|
132
|
+
reply_fn(CompletionStreamChunk(
|
|
133
|
+
parent_id=message_id,
|
|
134
|
+
chunk=CompletionItem(
|
|
135
|
+
content=content,
|
|
136
|
+
isIncomplete=not is_finished,
|
|
137
|
+
token=message_id,
|
|
138
|
+
),
|
|
139
|
+
done=is_finished,
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
return accumulated_response
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise Exception(f"LiteLLM streaming error: {str(e)}")
|
mito_ai/enterprise/utils.py
CHANGED
|
@@ -5,11 +5,25 @@
|
|
|
5
5
|
# Distributed under the terms of the The Mito Enterprise license.
|
|
6
6
|
|
|
7
7
|
from mito_ai.utils.version_utils import is_enterprise, is_mitosheet_private
|
|
8
|
-
from mito_ai.constants import
|
|
8
|
+
from mito_ai.constants import (
|
|
9
|
+
AZURE_OPENAI_API_KEY,
|
|
10
|
+
AZURE_OPENAI_ENDPOINT,
|
|
11
|
+
AZURE_OPENAI_API_VERSION,
|
|
12
|
+
AZURE_OPENAI_MODEL,
|
|
13
|
+
ABACUS_BASE_URL,
|
|
14
|
+
ABACUS_MODELS
|
|
15
|
+
)
|
|
9
16
|
|
|
10
17
|
def is_azure_openai_configured() -> bool:
|
|
11
18
|
"""
|
|
12
19
|
Azure OpenAI is only supported for Mito Enterprise users
|
|
13
20
|
"""
|
|
14
21
|
is_allowed_to_use_azure = is_enterprise() or is_mitosheet_private()
|
|
15
|
-
return all([is_allowed_to_use_azure, AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_MODEL])
|
|
22
|
+
return all([is_allowed_to_use_azure, AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_MODEL])
|
|
23
|
+
|
|
24
|
+
def is_abacus_configured() -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Abacus AI is only supported for Mito Enterprise users.
|
|
27
|
+
Checks if Abacus AI is configured with base URL and models.
|
|
28
|
+
"""
|
|
29
|
+
return all([is_enterprise(), ABACUS_BASE_URL, ABACUS_MODELS])
|
mito_ai/log/handlers.py
CHANGED
|
@@ -32,7 +32,7 @@ class LogHandler(APIHandler):
|
|
|
32
32
|
log_event = data['log_event']
|
|
33
33
|
params = data.get('params', {})
|
|
34
34
|
|
|
35
|
-
key_type = MITO_SERVER_KEY if self.key_type ==
|
|
35
|
+
key_type = MITO_SERVER_KEY if self.key_type == MITO_SERVER_KEY else USER_KEY
|
|
36
36
|
log(log_event, params, key_type=key_type)
|
|
37
37
|
|
|
38
38
|
|
mito_ai/openai_client.py
CHANGED
|
@@ -7,12 +7,13 @@ from typing import Any, AsyncGenerator, Callable, Dict, List, Optional, Union
|
|
|
7
7
|
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
8
8
|
import openai
|
|
9
9
|
from openai.types.chat import ChatCompletionMessageParam
|
|
10
|
-
from traitlets import Instance,
|
|
10
|
+
from traitlets import Instance, default, validate
|
|
11
11
|
from traitlets.config import LoggingConfigurable
|
|
12
12
|
|
|
13
13
|
from mito_ai import constants
|
|
14
|
-
from mito_ai.enterprise.utils import is_azure_openai_configured
|
|
14
|
+
from mito_ai.enterprise.utils import is_azure_openai_configured, is_abacus_configured
|
|
15
15
|
from mito_ai.logger import get_logger
|
|
16
|
+
from mito_ai.utils.model_utils import strip_router_prefix
|
|
16
17
|
from mito_ai.completions.models import (
|
|
17
18
|
AICapabilities,
|
|
18
19
|
CompletionError,
|
|
@@ -24,28 +25,17 @@ from mito_ai.completions.models import (
|
|
|
24
25
|
ResponseFormatInfo,
|
|
25
26
|
)
|
|
26
27
|
from mito_ai.utils.open_ai_utils import (
|
|
27
|
-
check_mito_server_quota,
|
|
28
28
|
get_ai_completion_from_mito_server,
|
|
29
29
|
get_open_ai_completion_function_params,
|
|
30
30
|
stream_ai_completion_from_mito_server,
|
|
31
31
|
)
|
|
32
|
-
from mito_ai.utils.server_limits import update_mito_server_quota
|
|
33
|
-
from mito_ai.utils.telemetry_utils import (
|
|
34
|
-
MITO_SERVER_KEY,
|
|
35
|
-
USER_KEY,
|
|
36
|
-
)
|
|
32
|
+
from mito_ai.utils.server_limits import update_mito_server_quota, check_mito_server_quota
|
|
37
33
|
|
|
38
34
|
OPENAI_MODEL_FALLBACK = "gpt-4.1"
|
|
39
35
|
|
|
40
36
|
class OpenAIClient(LoggingConfigurable):
|
|
41
37
|
"""Provide AI feature through OpenAI services."""
|
|
42
38
|
|
|
43
|
-
api_key = Unicode(
|
|
44
|
-
config=True,
|
|
45
|
-
allow_none=True,
|
|
46
|
-
help="OpenAI API key. Default value is read from the OPENAI_API_KEY environment variable.",
|
|
47
|
-
)
|
|
48
|
-
|
|
49
39
|
last_error = Instance(
|
|
50
40
|
CompletionError,
|
|
51
41
|
allow_none=True,
|
|
@@ -65,61 +55,6 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
65
55
|
super().__init__(log=get_logger(), **kwargs)
|
|
66
56
|
self.last_error = None
|
|
67
57
|
self._async_client: Optional[openai.AsyncOpenAI] = None
|
|
68
|
-
|
|
69
|
-
@default("api_key")
|
|
70
|
-
def _api_key_default(self) -> Optional[str]:
|
|
71
|
-
default_key = constants.OPENAI_API_KEY
|
|
72
|
-
return self._validate_api_key(default_key)
|
|
73
|
-
|
|
74
|
-
@validate("api_key")
|
|
75
|
-
def _validate_api_key(self, api_key: Optional[str]) -> Optional[str]:
|
|
76
|
-
if not api_key:
|
|
77
|
-
self.log.debug(
|
|
78
|
-
"No OpenAI API key provided; following back to Mito server API."
|
|
79
|
-
)
|
|
80
|
-
return None
|
|
81
|
-
|
|
82
|
-
client = openai.OpenAI(api_key=api_key)
|
|
83
|
-
try:
|
|
84
|
-
# Make an http request to OpenAI to make sure it works
|
|
85
|
-
client.models.list()
|
|
86
|
-
except openai.AuthenticationError as e:
|
|
87
|
-
self.log.warning(
|
|
88
|
-
"Invalid OpenAI API key provided.",
|
|
89
|
-
exc_info=e,
|
|
90
|
-
)
|
|
91
|
-
self.last_error = CompletionError.from_exception(
|
|
92
|
-
e,
|
|
93
|
-
hint="You're missing the OPENAI_API_KEY environment variable. Run the following code in your terminal to set the environment variable and then relaunch the jupyter server `export OPENAI_API_KEY=<your-api-key>`",
|
|
94
|
-
)
|
|
95
|
-
return None
|
|
96
|
-
except openai.PermissionDeniedError as e:
|
|
97
|
-
self.log.warning(
|
|
98
|
-
"Invalid OpenAI API key provided.",
|
|
99
|
-
exc_info=e,
|
|
100
|
-
)
|
|
101
|
-
self.last_error = CompletionError.from_exception(e)
|
|
102
|
-
return None
|
|
103
|
-
except openai.InternalServerError as e:
|
|
104
|
-
self.log.debug(
|
|
105
|
-
"Unable to get OpenAI models due to OpenAI error.", exc_info=e
|
|
106
|
-
)
|
|
107
|
-
return api_key
|
|
108
|
-
except openai.RateLimitError as e:
|
|
109
|
-
self.log.debug(
|
|
110
|
-
"Unable to get OpenAI models due to rate limit error.", exc_info=e
|
|
111
|
-
)
|
|
112
|
-
return api_key
|
|
113
|
-
except openai.APIConnectionError as e:
|
|
114
|
-
self.log.warning(
|
|
115
|
-
"Unable to connect to OpenAI API.",
|
|
116
|
-
exec_info=e,
|
|
117
|
-
)
|
|
118
|
-
self.last_error = CompletionError.from_exception(e)
|
|
119
|
-
return None
|
|
120
|
-
else:
|
|
121
|
-
self.log.debug("User OpenAI API key validated.")
|
|
122
|
-
return api_key
|
|
123
58
|
|
|
124
59
|
@property
|
|
125
60
|
def capabilities(self) -> AICapabilities:
|
|
@@ -133,7 +68,15 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
133
68
|
provider="Azure OpenAI",
|
|
134
69
|
)
|
|
135
70
|
|
|
136
|
-
if
|
|
71
|
+
if is_abacus_configured():
|
|
72
|
+
return AICapabilities(
|
|
73
|
+
configuration={
|
|
74
|
+
"model": "<dynamic>"
|
|
75
|
+
},
|
|
76
|
+
provider="Abacus AI",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if constants.OLLAMA_MODEL:
|
|
137
80
|
return AICapabilities(
|
|
138
81
|
configuration={
|
|
139
82
|
"model": constants.OLLAMA_MODEL
|
|
@@ -141,14 +84,12 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
141
84
|
provider="Ollama",
|
|
142
85
|
)
|
|
143
86
|
|
|
144
|
-
if
|
|
145
|
-
self._validate_api_key(self.api_key)
|
|
146
|
-
|
|
87
|
+
if constants.OPENAI_API_KEY:
|
|
147
88
|
return AICapabilities(
|
|
148
89
|
configuration={
|
|
149
|
-
"model":
|
|
90
|
+
"model": "<dynamic>"
|
|
150
91
|
},
|
|
151
|
-
provider="OpenAI
|
|
92
|
+
provider="OpenAI",
|
|
152
93
|
)
|
|
153
94
|
|
|
154
95
|
try:
|
|
@@ -169,19 +110,6 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
169
110
|
if not self._async_client or self._async_client.is_closed():
|
|
170
111
|
self._async_client = self._build_openai_client()
|
|
171
112
|
return self._async_client
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def key_type(self) -> str:
|
|
176
|
-
"""Returns the authentication key type being used."""
|
|
177
|
-
|
|
178
|
-
if self.api_key:
|
|
179
|
-
return USER_KEY
|
|
180
|
-
|
|
181
|
-
if constants.OLLAMA_MODEL:
|
|
182
|
-
return "ollama"
|
|
183
|
-
|
|
184
|
-
return MITO_SERVER_KEY
|
|
185
113
|
|
|
186
114
|
def _build_openai_client(self) -> Optional[Union[openai.AsyncOpenAI, openai.AsyncAzureOpenAI]]:
|
|
187
115
|
base_url = None
|
|
@@ -201,12 +129,16 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
201
129
|
timeout=self.timeout,
|
|
202
130
|
)
|
|
203
131
|
|
|
204
|
-
elif
|
|
132
|
+
elif is_abacus_configured():
|
|
133
|
+
base_url = constants.ABACUS_BASE_URL
|
|
134
|
+
llm_api_key = constants.ABACUS_API_KEY
|
|
135
|
+
self.log.debug(f"Using Abacus AI with base URL: {constants.ABACUS_BASE_URL}")
|
|
136
|
+
elif constants.OLLAMA_MODEL:
|
|
205
137
|
base_url = constants.OLLAMA_BASE_URL
|
|
206
138
|
llm_api_key = "ollama"
|
|
207
139
|
self.log.debug(f"Using Ollama with model: {constants.OLLAMA_MODEL}")
|
|
208
|
-
elif
|
|
209
|
-
llm_api_key =
|
|
140
|
+
elif constants.OPENAI_API_KEY:
|
|
141
|
+
llm_api_key = constants.OPENAI_API_KEY
|
|
210
142
|
self.log.debug("Using OpenAI with user-provided API key")
|
|
211
143
|
else:
|
|
212
144
|
self.log.warning("No valid API key or model configuration provided")
|
|
@@ -221,17 +153,25 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
221
153
|
)
|
|
222
154
|
return client
|
|
223
155
|
|
|
224
|
-
def
|
|
156
|
+
def _adjust_model_for_provider(self, model: str) -> str:
|
|
225
157
|
|
|
226
158
|
# If they have set an Azure OpenAI model, then we always use it
|
|
227
159
|
if is_azure_openai_configured() and constants.AZURE_OPENAI_MODEL is not None:
|
|
228
160
|
self.log.debug(f"Resolving to Azure OpenAI model: {constants.AZURE_OPENAI_MODEL}")
|
|
161
|
+
# TODO: We should update Azure so it works the way LiteLLM and Abacus do:
|
|
162
|
+
# when configured, we only show models from Azure in the UI.
|
|
229
163
|
return constants.AZURE_OPENAI_MODEL
|
|
230
164
|
|
|
231
165
|
# If they have set an Ollama model, then we use it
|
|
232
166
|
if constants.OLLAMA_MODEL is not None:
|
|
233
167
|
return constants.OLLAMA_MODEL
|
|
234
168
|
|
|
169
|
+
# If using Abacus, strip the "Abacus/" prefix from the model name
|
|
170
|
+
if is_abacus_configured() and model.lower().startswith('abacus/'):
|
|
171
|
+
stripped_model = strip_router_prefix(model)
|
|
172
|
+
self.log.debug(f"Stripping Abacus prefix: {model} -> {stripped_model}")
|
|
173
|
+
return stripped_model
|
|
174
|
+
|
|
235
175
|
# Otherwise, we use the model they provided
|
|
236
176
|
return model
|
|
237
177
|
|
|
@@ -262,11 +202,11 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
262
202
|
|
|
263
203
|
# Handle other providers as before
|
|
264
204
|
completion_function_params = get_open_ai_completion_function_params(
|
|
265
|
-
|
|
205
|
+
model, messages, False, response_format_info
|
|
266
206
|
)
|
|
267
207
|
|
|
268
208
|
# If they have set an Azure OpenAI or Ollama model, then we use it
|
|
269
|
-
completion_function_params["model"] = self.
|
|
209
|
+
completion_function_params["model"] = self._adjust_model_for_provider(completion_function_params["model"])
|
|
270
210
|
|
|
271
211
|
if self._active_async_client is not None:
|
|
272
212
|
response = await self._active_async_client.chat.completions.create(**completion_function_params)
|
|
@@ -313,10 +253,10 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
313
253
|
|
|
314
254
|
# Handle other providers as before
|
|
315
255
|
completion_function_params = get_open_ai_completion_function_params(
|
|
316
|
-
|
|
256
|
+
model, messages, True, response_format_info
|
|
317
257
|
)
|
|
318
258
|
|
|
319
|
-
completion_function_params["model"] = self.
|
|
259
|
+
completion_function_params["model"] = self._adjust_model_for_provider(completion_function_params["model"])
|
|
320
260
|
|
|
321
261
|
try:
|
|
322
262
|
if self._active_async_client is not None:
|