mito-ai 0.1.57__py3-none-any.whl → 0.1.58__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 +16 -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/constants.py +8 -1
- mito_ai/enterprise/__init__.py +1 -1
- mito_ai/enterprise/litellm_client.py +137 -0
- mito_ai/log/handlers.py +1 -1
- mito_ai/openai_client.py +10 -90
- mito_ai/{completions/providers.py → provider_manager.py} +157 -53
- 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 +72 -28
- mito_ai/tests/providers/test_anthropic_client.py +174 -16
- mito_ai/tests/providers/test_azure.py +13 -13
- 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_enterprise_mode.py +162 -0
- mito_ai/tests/test_model_utils.py +271 -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 +178 -0
- mito_ai/utils/open_ai_utils.py +0 -8
- mito_ai/utils/provider_utils.py +6 -28
- mito_ai/utils/telemetry_utils.py +14 -2
- {mito_ai-0.1.57.data → mito_ai-0.1.58.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +102 -102
- {mito_ai-0.1.57.data → mito_ai-0.1.58.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.57.data → mito_ai-0.1.58.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.58.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.03302cc521d72eb56b00.js +671 -75
- mito_ai-0.1.58.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.03302cc521d72eb56b00.js.map +1 -0
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.79c1ea8a3cda73a4cb6f.js → mito_ai-0.1.58.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.570df809a692f53a7ab7.js +17 -17
- mito_ai-0.1.57.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.79c1ea8a3cda73a4cb6f.js.map → mito_ai-0.1.58.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.570df809a692f53a7ab7.js.map +1 -1
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.58.dist-info}/METADATA +2 -1
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.58.dist-info}/RECORD +86 -79
- 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.58.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.58.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.58.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.58.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.58.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.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.58.data}/data/share/jupyter/labextensions/mito_ai/themes/mito_ai/index.css +0 -0
- {mito_ai-0.1.57.data → mito_ai-0.1.58.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.58.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.58.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.57.dist-info → mito_ai-0.1.58.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from unittest.mock import patch, MagicMock
|
|
6
|
+
from mito_ai.utils.model_utils import (
|
|
7
|
+
get_available_models,
|
|
8
|
+
get_fast_model_for_selected_model,
|
|
9
|
+
STANDARD_MODELS,
|
|
10
|
+
ANTHROPIC_MODEL_ORDER,
|
|
11
|
+
OPENAI_MODEL_ORDER,
|
|
12
|
+
GEMINI_MODEL_ORDER,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestGetAvailableModels:
|
|
17
|
+
"""Tests for get_available_models() function."""
|
|
18
|
+
|
|
19
|
+
@patch('mito_ai.utils.model_utils.is_enterprise')
|
|
20
|
+
@patch('mito_ai.utils.model_utils.constants')
|
|
21
|
+
def test_returns_litellm_models_when_enterprise_and_configured(self, mock_constants, mock_is_enterprise):
|
|
22
|
+
"""Test that LiteLLM models are returned when enterprise mode is enabled and LiteLLM is configured."""
|
|
23
|
+
mock_is_enterprise.return_value = True
|
|
24
|
+
mock_constants.LITELLM_BASE_URL = "https://litellm-server.com"
|
|
25
|
+
mock_constants.LITELLM_MODELS = ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
|
|
26
|
+
|
|
27
|
+
result = get_available_models()
|
|
28
|
+
|
|
29
|
+
assert result == ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
|
|
30
|
+
|
|
31
|
+
@patch('mito_ai.utils.model_utils.is_enterprise')
|
|
32
|
+
@patch('mito_ai.utils.model_utils.constants')
|
|
33
|
+
def test_returns_standard_models_when_not_enterprise(self, mock_constants, mock_is_enterprise):
|
|
34
|
+
"""Test that standard models are returned when enterprise mode is not enabled."""
|
|
35
|
+
mock_is_enterprise.return_value = False
|
|
36
|
+
|
|
37
|
+
result = get_available_models()
|
|
38
|
+
|
|
39
|
+
assert result == STANDARD_MODELS
|
|
40
|
+
|
|
41
|
+
@patch('mito_ai.utils.model_utils.is_enterprise')
|
|
42
|
+
@patch('mito_ai.utils.model_utils.constants')
|
|
43
|
+
def test_returns_standard_models_when_enterprise_but_no_litellm(self, mock_constants, mock_is_enterprise):
|
|
44
|
+
"""Test that standard models are returned when enterprise mode is enabled but LiteLLM is not configured."""
|
|
45
|
+
mock_is_enterprise.return_value = True
|
|
46
|
+
mock_constants.LITELLM_BASE_URL = None
|
|
47
|
+
mock_constants.LITELLM_MODELS = []
|
|
48
|
+
|
|
49
|
+
result = get_available_models()
|
|
50
|
+
|
|
51
|
+
assert result == STANDARD_MODELS
|
|
52
|
+
|
|
53
|
+
@patch('mito_ai.utils.model_utils.is_enterprise')
|
|
54
|
+
@patch('mito_ai.utils.model_utils.constants')
|
|
55
|
+
def test_returns_standard_models_when_enterprise_but_no_base_url(self, mock_constants, mock_is_enterprise):
|
|
56
|
+
"""Test that standard models are returned when enterprise mode is enabled but LITELLM_BASE_URL is not set."""
|
|
57
|
+
mock_is_enterprise.return_value = True
|
|
58
|
+
mock_constants.LITELLM_BASE_URL = None
|
|
59
|
+
mock_constants.LITELLM_MODELS = ["openai/gpt-4o"]
|
|
60
|
+
|
|
61
|
+
result = get_available_models()
|
|
62
|
+
|
|
63
|
+
assert result == STANDARD_MODELS
|
|
64
|
+
|
|
65
|
+
@patch('mito_ai.utils.model_utils.is_enterprise')
|
|
66
|
+
@patch('mito_ai.utils.model_utils.constants')
|
|
67
|
+
def test_returns_standard_models_when_enterprise_but_no_models(self, mock_constants, mock_is_enterprise):
|
|
68
|
+
"""Test that standard models are returned when enterprise mode is enabled but LITELLM_MODELS is empty."""
|
|
69
|
+
mock_is_enterprise.return_value = True
|
|
70
|
+
mock_constants.LITELLM_BASE_URL = "https://litellm-server.com"
|
|
71
|
+
mock_constants.LITELLM_MODELS = []
|
|
72
|
+
|
|
73
|
+
result = get_available_models()
|
|
74
|
+
|
|
75
|
+
assert result == STANDARD_MODELS
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestGetFastModelForSelectedModel:
|
|
79
|
+
"""Tests for get_fast_model_for_selected_model() function."""
|
|
80
|
+
|
|
81
|
+
def test_anthropic_sonnet_returns_haiku(self):
|
|
82
|
+
"""Test that Claude Sonnet returns Claude Haiku (fastest Anthropic model)."""
|
|
83
|
+
result = get_fast_model_for_selected_model("claude-sonnet-4-5-20250929")
|
|
84
|
+
assert result == "claude-haiku-4-5-20251001"
|
|
85
|
+
|
|
86
|
+
def test_anthropic_haiku_returns_haiku(self):
|
|
87
|
+
"""Test that Claude Haiku returns itself (already fastest)."""
|
|
88
|
+
result = get_fast_model_for_selected_model("claude-haiku-4-5-20251001")
|
|
89
|
+
assert result == "claude-haiku-4-5-20251001"
|
|
90
|
+
|
|
91
|
+
def test_openai_gpt_4_1_returns_gpt_4_1(self):
|
|
92
|
+
"""Test that GPT 4.1 returns itself (already fastest)."""
|
|
93
|
+
result = get_fast_model_for_selected_model("gpt-4.1")
|
|
94
|
+
assert result == "gpt-4.1"
|
|
95
|
+
|
|
96
|
+
def test_openai_gpt_5_2_returns_gpt_4_1(self):
|
|
97
|
+
"""Test that GPT 5.2 returns GPT 4.1 (fastest OpenAI model)."""
|
|
98
|
+
result = get_fast_model_for_selected_model("gpt-5.2")
|
|
99
|
+
assert result == "gpt-4.1"
|
|
100
|
+
|
|
101
|
+
def test_gemini_pro_returns_flash(self):
|
|
102
|
+
"""Test that Gemini Pro returns Gemini Flash (fastest Gemini model)."""
|
|
103
|
+
result = get_fast_model_for_selected_model("gemini-3-pro-preview")
|
|
104
|
+
assert result == "gemini-3-flash-preview"
|
|
105
|
+
|
|
106
|
+
def test_gemini_flash_returns_flash(self):
|
|
107
|
+
"""Test that Gemini Flash returns itself (already fastest)."""
|
|
108
|
+
result = get_fast_model_for_selected_model("gemini-3-flash-preview")
|
|
109
|
+
assert result == "gemini-3-flash-preview"
|
|
110
|
+
|
|
111
|
+
@patch('mito_ai.utils.model_utils.get_available_models')
|
|
112
|
+
@pytest.mark.parametrize(
|
|
113
|
+
"selected_model,available_models,expected_result",
|
|
114
|
+
[
|
|
115
|
+
# Test case 1: LiteLLM OpenAI model returns fastest overall
|
|
116
|
+
(
|
|
117
|
+
"openai/gpt-5.2",
|
|
118
|
+
["openai/gpt-4.1", "openai/gpt-5.2", "anthropic/claude-sonnet-4-5-20250929"],
|
|
119
|
+
"openai/gpt-4.1",
|
|
120
|
+
),
|
|
121
|
+
# Test case 2: LiteLLM Anthropic model returns fastest overall
|
|
122
|
+
(
|
|
123
|
+
"anthropic/claude-sonnet-4-5-20250929",
|
|
124
|
+
["openai/gpt-4.1", "anthropic/claude-sonnet-4-5-20250929", "anthropic/claude-haiku-4-5-20251001"],
|
|
125
|
+
"openai/gpt-4.1",
|
|
126
|
+
),
|
|
127
|
+
# Test case 3: LiteLLM Google model returns fastest overall
|
|
128
|
+
(
|
|
129
|
+
"google/gemini-3-pro-preview",
|
|
130
|
+
["google/gemini-3-pro-preview", "google/gemini-3-flash-preview"],
|
|
131
|
+
"google/gemini-3-flash-preview",
|
|
132
|
+
),
|
|
133
|
+
# Test case 4: Unknown LiteLLM model returns fastest known
|
|
134
|
+
(
|
|
135
|
+
"unknown/provider/model",
|
|
136
|
+
["openai/gpt-4.1", "unknown/provider/model"],
|
|
137
|
+
"openai/gpt-4.1",
|
|
138
|
+
),
|
|
139
|
+
# Test case 5: Single LiteLLM model returns itself
|
|
140
|
+
(
|
|
141
|
+
"openai/gpt-4o",
|
|
142
|
+
["openai/gpt-4o"],
|
|
143
|
+
"openai/gpt-4o",
|
|
144
|
+
),
|
|
145
|
+
# Test case 6: Cross-provider comparison - OpenAI is faster
|
|
146
|
+
(
|
|
147
|
+
"anthropic/claude-sonnet-4-5-20250929",
|
|
148
|
+
[
|
|
149
|
+
"openai/gpt-4.1", # Index 0 in OPENAI_MODEL_ORDER
|
|
150
|
+
"anthropic/claude-sonnet-4-5-20250929", # Index 1 in ANTHROPIC_MODEL_ORDER
|
|
151
|
+
],
|
|
152
|
+
"openai/gpt-4.1",
|
|
153
|
+
),
|
|
154
|
+
# Test case 7: Cross-provider comparison - Anthropic is faster
|
|
155
|
+
(
|
|
156
|
+
"openai/gpt-5.2",
|
|
157
|
+
[
|
|
158
|
+
"openai/gpt-5.2", # Index 1 in OPENAI_MODEL_ORDER
|
|
159
|
+
"anthropic/claude-haiku-4-5-20251001", # Index 0 in ANTHROPIC_MODEL_ORDER
|
|
160
|
+
],
|
|
161
|
+
"anthropic/claude-haiku-4-5-20251001",
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
|
+
ids=[
|
|
165
|
+
"litellm_openai_model_returns_fastest_overall",
|
|
166
|
+
"litellm_anthropic_model_returns_fastest_overall",
|
|
167
|
+
"litellm_google_model_returns_fastest_overall",
|
|
168
|
+
"litellm_unknown_model_returns_fastest_known",
|
|
169
|
+
"litellm_single_model_returns_itself",
|
|
170
|
+
"litellm_cross_provider_comparison_openai_faster",
|
|
171
|
+
"litellm_returns_fastest_when_anthropic_is_faster",
|
|
172
|
+
]
|
|
173
|
+
)
|
|
174
|
+
def test_litellm_model_returns_fastest(
|
|
175
|
+
self,
|
|
176
|
+
mock_get_available_models,
|
|
177
|
+
selected_model,
|
|
178
|
+
available_models,
|
|
179
|
+
expected_result,
|
|
180
|
+
):
|
|
181
|
+
"""Test that LiteLLM models return fastest model from all available models."""
|
|
182
|
+
mock_get_available_models.return_value = available_models
|
|
183
|
+
|
|
184
|
+
result = get_fast_model_for_selected_model(selected_model)
|
|
185
|
+
|
|
186
|
+
assert result == expected_result
|
|
187
|
+
|
|
188
|
+
def test_unknown_standard_model_returns_itself(self):
|
|
189
|
+
"""Test that unknown standard model returns itself."""
|
|
190
|
+
result = get_fast_model_for_selected_model("unknown-model")
|
|
191
|
+
assert result == "unknown-model"
|
|
192
|
+
|
|
193
|
+
def test_claude_model_not_in_order_returns_fastest_anthropic(self):
|
|
194
|
+
"""Test that a Claude model not in ANTHROPIC_MODEL_ORDER still returns fastest Anthropic model."""
|
|
195
|
+
# Test with a Claude model that isn't in the order list
|
|
196
|
+
result = get_fast_model_for_selected_model("claude-3-opus-20240229")
|
|
197
|
+
# Should return fastest Anthropic model (claude-haiku-4-5-20251001)
|
|
198
|
+
assert result == "claude-haiku-4-5-20251001"
|
|
199
|
+
assert result.startswith("claude")
|
|
200
|
+
|
|
201
|
+
def test_gpt_model_not_in_order_returns_fastest_openai(self):
|
|
202
|
+
"""Test that a GPT model not in OPENAI_MODEL_ORDER still returns fastest OpenAI model."""
|
|
203
|
+
# Test with a GPT model that isn't in the order list
|
|
204
|
+
result = get_fast_model_for_selected_model("gpt-4o-mini")
|
|
205
|
+
# Should return fastest OpenAI model (gpt-4.1)
|
|
206
|
+
assert result == "gpt-4.1"
|
|
207
|
+
assert result.startswith("gpt")
|
|
208
|
+
|
|
209
|
+
def test_gemini_model_not_in_order_returns_fastest_gemini(self):
|
|
210
|
+
"""Test that a Gemini model not in GEMINI_MODEL_ORDER still returns fastest Gemini model."""
|
|
211
|
+
# Test with a Gemini model that isn't in the order list
|
|
212
|
+
result = get_fast_model_for_selected_model("gemini-1.5-pro")
|
|
213
|
+
# Should return fastest Gemini model (gemini-3-flash-preview)
|
|
214
|
+
assert result == "gemini-3-flash-preview"
|
|
215
|
+
assert result.startswith("gemini")
|
|
216
|
+
|
|
217
|
+
def test_claude_model_variations_return_same_provider(self):
|
|
218
|
+
"""Test that various Claude model name variations return Anthropic models."""
|
|
219
|
+
test_cases = [
|
|
220
|
+
"claude-3-5-sonnet",
|
|
221
|
+
"claude-3-opus",
|
|
222
|
+
"claude-instant",
|
|
223
|
+
"claude-v2",
|
|
224
|
+
]
|
|
225
|
+
for model in test_cases:
|
|
226
|
+
result = get_fast_model_for_selected_model(model)
|
|
227
|
+
# Should always return an Anthropic model (starts with "claude")
|
|
228
|
+
assert result.startswith("claude"), f"Model {model} should return Anthropic model, got {result}"
|
|
229
|
+
# Should return the fastest Anthropic model
|
|
230
|
+
assert result == "claude-haiku-4-5-20251001", f"Model {model} should return fastest Anthropic model"
|
|
231
|
+
|
|
232
|
+
def test_gpt_model_variations_return_same_provider(self):
|
|
233
|
+
"""Test that various GPT model name variations return OpenAI models."""
|
|
234
|
+
test_cases = [
|
|
235
|
+
"gpt-4o",
|
|
236
|
+
"gpt-4-turbo",
|
|
237
|
+
"gpt-3.5-turbo",
|
|
238
|
+
"gpt-4o-mini",
|
|
239
|
+
]
|
|
240
|
+
for model in test_cases:
|
|
241
|
+
result = get_fast_model_for_selected_model(model)
|
|
242
|
+
# Should always return an OpenAI model (starts with "gpt")
|
|
243
|
+
assert result.startswith("gpt"), f"Model {model} should return OpenAI model, got {result}"
|
|
244
|
+
# Should return the fastest OpenAI model
|
|
245
|
+
assert result == "gpt-4.1", f"Model {model} should return fastest OpenAI model"
|
|
246
|
+
|
|
247
|
+
def test_gemini_model_variations_return_same_provider(self):
|
|
248
|
+
"""Test that various Gemini model name variations return Gemini models."""
|
|
249
|
+
test_cases = [
|
|
250
|
+
"gemini-1.5-pro",
|
|
251
|
+
"gemini-1.5-flash",
|
|
252
|
+
"gemini-pro",
|
|
253
|
+
"gemini-ultra",
|
|
254
|
+
]
|
|
255
|
+
for model in test_cases:
|
|
256
|
+
result = get_fast_model_for_selected_model(model)
|
|
257
|
+
# Should always return a Gemini model (starts with "gemini")
|
|
258
|
+
assert result.startswith("gemini"), f"Model {model} should return Gemini model, got {result}"
|
|
259
|
+
# Should return the fastest Gemini model
|
|
260
|
+
assert result == "gemini-3-flash-preview", f"Model {model} should return fastest Gemini model"
|
|
261
|
+
|
|
262
|
+
def test_case_insensitive_provider_matching(self):
|
|
263
|
+
"""Test that provider matching is case-insensitive."""
|
|
264
|
+
test_cases = [
|
|
265
|
+
("CLAUDE-sonnet-4-5-20250929", "claude-haiku-4-5-20251001"),
|
|
266
|
+
("GPT-4.1", "gpt-4.1"),
|
|
267
|
+
("GEMINI-3-flash-preview", "gemini-3-flash-preview"),
|
|
268
|
+
]
|
|
269
|
+
for model, expected in test_cases:
|
|
270
|
+
result = get_fast_model_for_selected_model(model)
|
|
271
|
+
assert result == expected, f"Case-insensitive matching failed for {model}"
|
mito_ai/utils/anthropic_utils.py
CHANGED
|
@@ -5,7 +5,6 @@ import anthropic
|
|
|
5
5
|
from typing import Any, Dict, List, Optional, Union, AsyncGenerator, Tuple, Callable
|
|
6
6
|
from anthropic.types import MessageParam, TextBlockParam, ToolUnionParam
|
|
7
7
|
from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
|
|
8
|
-
from mito_ai.utils.provider_utils import does_message_require_fast_model
|
|
9
8
|
from mito_ai.completions.models import AgentResponse, MessageType, ResponseFormatInfo, CompletionReply, CompletionStreamChunk
|
|
10
9
|
from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
|
|
11
10
|
from mito_ai.utils.db import get_user_field
|
|
@@ -20,6 +19,7 @@ max_retries = 1
|
|
|
20
19
|
|
|
21
20
|
FAST_ANTHROPIC_MODEL = "claude-haiku-4-5-20251001" # This should be in sync with ModelSelector.tsx
|
|
22
21
|
LARGE_CONTEXT_MODEL = "claude-sonnet-4-5-20250929" # This should be in sync with ModelSelector.tsx
|
|
22
|
+
EXTENDED_CONTEXT_BETA = "context-1m-2025-08-07" # Beta feature for extended context window support
|
|
23
23
|
|
|
24
24
|
def does_message_exceed_max_tokens(system: Union[str, List[TextBlockParam], anthropic.Omit], messages: List[MessageParam]) -> bool:
|
|
25
25
|
token_estimation = get_rough_token_estimatation_anthropic(system, messages)
|
|
@@ -36,10 +36,6 @@ def select_correct_model(default_model: str, message_type: MessageType, system:
|
|
|
36
36
|
# but not haiku models
|
|
37
37
|
return LARGE_CONTEXT_MODEL
|
|
38
38
|
|
|
39
|
-
message_requires_fast_model = does_message_require_fast_model(message_type)
|
|
40
|
-
if message_requires_fast_model:
|
|
41
|
-
return FAST_ANTHROPIC_MODEL
|
|
42
|
-
|
|
43
39
|
return default_model
|
|
44
40
|
|
|
45
41
|
def _prepare_anthropic_request_data_and_headers(
|
|
@@ -66,7 +62,7 @@ def _prepare_anthropic_request_data_and_headers(
|
|
|
66
62
|
"max_tokens": max_tokens,
|
|
67
63
|
"temperature": temperature,
|
|
68
64
|
"messages": messages,
|
|
69
|
-
"betas": [
|
|
65
|
+
"betas": [EXTENDED_CONTEXT_BETA]
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
# Add system to inner_data only if it is not anthropic.Omit
|
|
@@ -173,6 +169,12 @@ def get_anthropic_completion_function_params(
|
|
|
173
169
|
"messages": messages,
|
|
174
170
|
"system": system,
|
|
175
171
|
}
|
|
172
|
+
|
|
173
|
+
# Enable extended context beta when using LARGE_CONTEXT_MODEL
|
|
174
|
+
# This is required for messages exceeding the standard context limit
|
|
175
|
+
if model == LARGE_CONTEXT_MODEL:
|
|
176
|
+
provider_data["betas"] = [EXTENDED_CONTEXT_BETA]
|
|
177
|
+
|
|
176
178
|
if tools:
|
|
177
179
|
provider_data["tools"] = tools
|
|
178
180
|
if response_format_info and response_format_info.name == "agent_response":
|
mito_ai/utils/gemini_utils.py
CHANGED
|
@@ -8,7 +8,6 @@ from typing import Any, Dict, List, Optional, Callable, Union, AsyncGenerator, T
|
|
|
8
8
|
from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
|
|
9
9
|
from mito_ai.completions.models import AgentResponse, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
|
|
10
10
|
from mito_ai.constants import MITO_GEMINI_URL
|
|
11
|
-
from mito_ai.utils.provider_utils import does_message_require_fast_model
|
|
12
11
|
from mito_ai.utils.utils import _create_http_client
|
|
13
12
|
|
|
14
13
|
timeout = 30
|
|
@@ -114,8 +113,6 @@ def get_gemini_completion_function_params(
|
|
|
114
113
|
Build the provider_data dict for Gemini completions, mirroring the OpenAI/Anthropic approach.
|
|
115
114
|
Only includes fields needed for the Gemini API.
|
|
116
115
|
"""
|
|
117
|
-
message_requires_fast_model = does_message_require_fast_model(message_type)
|
|
118
|
-
model = FAST_GEMINI_MODEL if message_requires_fast_model else model
|
|
119
116
|
|
|
120
117
|
provider_data: Dict[str, Any] = {
|
|
121
118
|
"model": model,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) Saga Inc.
|
|
4
|
+
# Distributed under the terms of the The Mito Enterprise license.
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, List, Optional
|
|
7
|
+
from openai.types.chat import ChatCompletionMessageParam
|
|
8
|
+
import copy
|
|
9
|
+
|
|
10
|
+
from mito_ai import constants
|
|
11
|
+
from mito_ai.completions.models import ResponseFormatInfo
|
|
12
|
+
from mito_ai.utils.version_utils import is_enterprise
|
|
13
|
+
|
|
14
|
+
def is_litellm_configured() -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Check if LiteLLM is configured for system use.
|
|
17
|
+
|
|
18
|
+
Per enterprise documentation, LITELLM_API_KEY is user-controlled and optional
|
|
19
|
+
for system configuration. This function only checks system-level configuration
|
|
20
|
+
(BASE_URL and MODELS), not user-specific API keys.
|
|
21
|
+
"""
|
|
22
|
+
return all([constants.LITELLM_BASE_URL, constants.LITELLM_MODELS, is_enterprise()])
|
|
23
|
+
|
|
24
|
+
def get_litellm_completion_function_params(
|
|
25
|
+
model: str,
|
|
26
|
+
messages: List[ChatCompletionMessageParam],
|
|
27
|
+
api_key: Optional[str],
|
|
28
|
+
api_base: str,
|
|
29
|
+
timeout: int,
|
|
30
|
+
stream: bool,
|
|
31
|
+
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Prepare parameters for LiteLLM completion requests.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
model: Model name with provider prefix (e.g., "openai/gpt-4o")
|
|
38
|
+
messages: List of chat messages
|
|
39
|
+
api_key: Optional API key for authentication
|
|
40
|
+
api_base: Base URL for the LiteLLM server
|
|
41
|
+
timeout: Request timeout in seconds
|
|
42
|
+
stream: Whether to stream the response
|
|
43
|
+
response_format_info: Optional response format specification
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dictionary of parameters ready to be passed to litellm.acompletion()
|
|
47
|
+
"""
|
|
48
|
+
params: Dict[str, Any] = {
|
|
49
|
+
"model": model,
|
|
50
|
+
"messages": messages,
|
|
51
|
+
"api_key": api_key,
|
|
52
|
+
"api_base": api_base,
|
|
53
|
+
"timeout": timeout,
|
|
54
|
+
"stream": stream,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Handle response format if specified
|
|
58
|
+
if response_format_info:
|
|
59
|
+
# LiteLLM supports response_format for structured outputs
|
|
60
|
+
if hasattr(response_format_info.format, 'model_json_schema'):
|
|
61
|
+
# Pydantic model - get JSON schema
|
|
62
|
+
# Make a deep copy to avoid mutating the original schema
|
|
63
|
+
schema = copy.deepcopy(response_format_info.format.model_json_schema())
|
|
64
|
+
|
|
65
|
+
# Add additionalProperties: False to the top-level schema
|
|
66
|
+
# This is required by OpenAI's JSON schema mode
|
|
67
|
+
schema["additionalProperties"] = False
|
|
68
|
+
|
|
69
|
+
# Nested object definitions in $defs need to have additionalProperties set to False also
|
|
70
|
+
if "$defs" in schema:
|
|
71
|
+
for def_name, def_schema in schema["$defs"].items():
|
|
72
|
+
if def_schema.get("type") == "object":
|
|
73
|
+
def_schema["additionalProperties"] = False
|
|
74
|
+
|
|
75
|
+
params["response_format"] = {
|
|
76
|
+
"type": "json_schema",
|
|
77
|
+
"json_schema": {
|
|
78
|
+
"name": response_format_info.name,
|
|
79
|
+
"schema": schema,
|
|
80
|
+
"strict": True
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return params
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
from typing import List, Tuple, Union, Optional, cast
|
|
5
|
+
from mito_ai import constants
|
|
6
|
+
from mito_ai.utils.version_utils import is_enterprise
|
|
7
|
+
|
|
8
|
+
# Model ordering: [fastest, ..., slowest] for each provider
|
|
9
|
+
ANTHROPIC_MODEL_ORDER = [
|
|
10
|
+
"claude-haiku-4-5-20251001", # Fastest
|
|
11
|
+
"claude-sonnet-4-5-20250929", # Slower
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
OPENAI_MODEL_ORDER = [
|
|
15
|
+
"gpt-4.1", # Fastest
|
|
16
|
+
"gpt-5",
|
|
17
|
+
"gpt-5.2", # Slower
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
GEMINI_MODEL_ORDER = [
|
|
21
|
+
"gemini-3-flash-preview", # Fastest
|
|
22
|
+
"gemini-3-pro-preview", # Slower
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Standard model names (used when not in enterprise mode or when LiteLLM is not configured)
|
|
26
|
+
STANDARD_MODELS = [
|
|
27
|
+
"gpt-4.1",
|
|
28
|
+
"gpt-5.2",
|
|
29
|
+
"claude-sonnet-4-5-20250929",
|
|
30
|
+
"claude-haiku-4-5-20251001",
|
|
31
|
+
"gemini-3-flash-preview",
|
|
32
|
+
"gemini-3-pro-preview",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_available_models() -> List[str]:
|
|
37
|
+
"""
|
|
38
|
+
Determine which models are available based on enterprise mode and LiteLLM configuration.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of available model names. If enterprise mode is enabled AND LiteLLM is configured,
|
|
42
|
+
returns LiteLLM models. Otherwise, returns standard models.
|
|
43
|
+
"""
|
|
44
|
+
# Check if enterprise mode is enabled AND LiteLLM is configured
|
|
45
|
+
if is_enterprise() and constants.LITELLM_BASE_URL and constants.LITELLM_MODELS:
|
|
46
|
+
# Return LiteLLM models (with provider prefixes)
|
|
47
|
+
return constants.LITELLM_MODELS
|
|
48
|
+
else:
|
|
49
|
+
# Return standard models
|
|
50
|
+
return STANDARD_MODELS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_fast_model_for_selected_model(selected_model: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Get the fastest model for the client of the selected model.
|
|
56
|
+
|
|
57
|
+
- For standard providers, returns the first (fastest) model from that provider's order.
|
|
58
|
+
- For LiteLLM models, finds the fastest available model from LiteLLM by comparing indices in the model order lists.
|
|
59
|
+
"""
|
|
60
|
+
# Check if this is a LiteLLM model (has provider prefix like "openai/gpt-4o")
|
|
61
|
+
if "/" in selected_model:
|
|
62
|
+
|
|
63
|
+
# Find the fastest model from available LiteLLM models
|
|
64
|
+
available_models = get_available_models()
|
|
65
|
+
if not available_models:
|
|
66
|
+
return selected_model
|
|
67
|
+
|
|
68
|
+
# Filter to only LiteLLM models (those with "/") before splitting
|
|
69
|
+
litellm_models = [model for model in available_models if "/" in model]
|
|
70
|
+
if not litellm_models:
|
|
71
|
+
return selected_model
|
|
72
|
+
|
|
73
|
+
available_provider_model_pairs: List[List[str]] = [model.split("/", 1) for model in litellm_models]
|
|
74
|
+
|
|
75
|
+
# Get indices for all pairs and filter out None indices (unknown models)
|
|
76
|
+
pairs_with_indices = [(pair, get_model_order_index(pair)) for pair in available_provider_model_pairs]
|
|
77
|
+
valid_pairs_with_indices = [(pair, index) for pair, index in pairs_with_indices if index is not None]
|
|
78
|
+
|
|
79
|
+
if not valid_pairs_with_indices:
|
|
80
|
+
return selected_model
|
|
81
|
+
|
|
82
|
+
# Find the pair with the minimum index (fastest model)
|
|
83
|
+
fastest_pair, _ = min(valid_pairs_with_indices, key=lambda x: x[1])
|
|
84
|
+
fastest_model = f"{fastest_pair[0]}/{fastest_pair[1]}"
|
|
85
|
+
|
|
86
|
+
# If we found a fastest model, return it. Otherwise, use the selected model
|
|
87
|
+
if fastest_model:
|
|
88
|
+
return fastest_model
|
|
89
|
+
else:
|
|
90
|
+
return selected_model
|
|
91
|
+
|
|
92
|
+
# Standard provider logic - ensure we return a model from the same provider
|
|
93
|
+
model_lower = selected_model.lower()
|
|
94
|
+
|
|
95
|
+
# Determine provider and get fastest model
|
|
96
|
+
if model_lower.startswith('claude'):
|
|
97
|
+
return ANTHROPIC_MODEL_ORDER[0]
|
|
98
|
+
elif model_lower.startswith('gpt'):
|
|
99
|
+
return OPENAI_MODEL_ORDER[0]
|
|
100
|
+
elif model_lower.startswith('gemini'):
|
|
101
|
+
return GEMINI_MODEL_ORDER[0]
|
|
102
|
+
|
|
103
|
+
return selected_model
|
|
104
|
+
|
|
105
|
+
def get_smartest_model_for_selected_model(selected_model: str) -> str:
|
|
106
|
+
"""
|
|
107
|
+
Get the smartest model for the client of the selected model.
|
|
108
|
+
|
|
109
|
+
- For standard providers, returns the last (smartest) model from that provider's order.
|
|
110
|
+
- For LiteLLM models, finds the smartest available model from LiteLLM by comparing indices in the model order lists.
|
|
111
|
+
"""
|
|
112
|
+
# Check if this is a LiteLLM model (has provider prefix like "openai/gpt-4o")
|
|
113
|
+
if "/" in selected_model:
|
|
114
|
+
|
|
115
|
+
# Extract provider from selected model
|
|
116
|
+
selected_provider, _ = selected_model.split("/", 1)
|
|
117
|
+
|
|
118
|
+
# Find the smartest model from available LiteLLM models
|
|
119
|
+
available_models = get_available_models()
|
|
120
|
+
if not available_models:
|
|
121
|
+
return selected_model
|
|
122
|
+
|
|
123
|
+
# Filter to only LiteLLM models (those with "/")
|
|
124
|
+
litellm_models = [model for model in available_models if "/" in model and model.startswith(f"{selected_provider}/")]
|
|
125
|
+
if not litellm_models:
|
|
126
|
+
return selected_model
|
|
127
|
+
|
|
128
|
+
available_provider_model_pairs: List[List[str]] = [model.split("/", 1) for model in litellm_models]
|
|
129
|
+
|
|
130
|
+
# Get indices for all pairs and filter out None indices (unknown models)
|
|
131
|
+
pairs_with_indices = [(pair, get_model_order_index(pair)) for pair in available_provider_model_pairs]
|
|
132
|
+
valid_pairs_with_indices = [(pair, index) for pair, index in pairs_with_indices if index is not None]
|
|
133
|
+
|
|
134
|
+
if not valid_pairs_with_indices:
|
|
135
|
+
return selected_model
|
|
136
|
+
|
|
137
|
+
# Find the pair with the maximum index (smartest model)
|
|
138
|
+
smartest_pair, _ = max(valid_pairs_with_indices, key=lambda x: x[1])
|
|
139
|
+
smartest_model = f"{smartest_pair[0]}/{smartest_pair[1]}"
|
|
140
|
+
|
|
141
|
+
# If we found a smartest model, return it. Otherwise, use the selected model
|
|
142
|
+
if smartest_model:
|
|
143
|
+
return smartest_model
|
|
144
|
+
else:
|
|
145
|
+
return selected_model
|
|
146
|
+
|
|
147
|
+
# Standard provider logic
|
|
148
|
+
model_lower = selected_model.lower()
|
|
149
|
+
|
|
150
|
+
# Determine provider and get smartest model
|
|
151
|
+
if model_lower.startswith('claude'):
|
|
152
|
+
return ANTHROPIC_MODEL_ORDER[-1]
|
|
153
|
+
elif model_lower.startswith('gpt'):
|
|
154
|
+
return OPENAI_MODEL_ORDER[-1]
|
|
155
|
+
elif model_lower.startswith('gemini'):
|
|
156
|
+
return GEMINI_MODEL_ORDER[-1]
|
|
157
|
+
|
|
158
|
+
return selected_model
|
|
159
|
+
|
|
160
|
+
def get_model_order_index(pair: List[str]) -> Optional[int]:
|
|
161
|
+
provider, model_name = pair
|
|
162
|
+
if provider == "openai":
|
|
163
|
+
try:
|
|
164
|
+
return OPENAI_MODEL_ORDER.index(model_name)
|
|
165
|
+
except ValueError:
|
|
166
|
+
return None
|
|
167
|
+
elif provider == "anthropic":
|
|
168
|
+
try:
|
|
169
|
+
return ANTHROPIC_MODEL_ORDER.index(model_name)
|
|
170
|
+
except ValueError:
|
|
171
|
+
return None
|
|
172
|
+
elif provider == "google":
|
|
173
|
+
try:
|
|
174
|
+
return GEMINI_MODEL_ORDER.index(model_name)
|
|
175
|
+
except ValueError:
|
|
176
|
+
return None
|
|
177
|
+
else:
|
|
178
|
+
return None
|
mito_ai/utils/open_ai_utils.py
CHANGED
|
@@ -11,7 +11,6 @@ import json
|
|
|
11
11
|
import time
|
|
12
12
|
from typing import Any, Dict, List, Optional, Final, Union, AsyncGenerator, Tuple, Callable
|
|
13
13
|
from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
|
|
14
|
-
from mito_ai.utils.provider_utils import does_message_require_fast_model
|
|
15
14
|
from tornado.httpclient import AsyncHTTPClient
|
|
16
15
|
from openai.types.chat import ChatCompletionMessageParam
|
|
17
16
|
|
|
@@ -153,19 +152,12 @@ async def stream_ai_completion_from_mito_server(
|
|
|
153
152
|
|
|
154
153
|
|
|
155
154
|
def get_open_ai_completion_function_params(
|
|
156
|
-
message_type: MessageType,
|
|
157
155
|
model: str,
|
|
158
156
|
messages: List[ChatCompletionMessageParam],
|
|
159
157
|
stream: bool,
|
|
160
158
|
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
161
159
|
) -> Dict[str, Any]:
|
|
162
160
|
|
|
163
|
-
print("MESSAGE TYPE: ", message_type)
|
|
164
|
-
message_requires_fast_model = does_message_require_fast_model(message_type)
|
|
165
|
-
model = FAST_OPENAI_MODEL if message_requires_fast_model else model
|
|
166
|
-
|
|
167
|
-
print(f"model: {model}")
|
|
168
|
-
|
|
169
161
|
completion_function_params = {
|
|
170
162
|
"model": model,
|
|
171
163
|
"stream": stream,
|
mito_ai/utils/provider_utils.py
CHANGED
|
@@ -13,6 +13,12 @@ def get_model_provider(model: str) -> Union[str, None]:
|
|
|
13
13
|
if not model:
|
|
14
14
|
return None
|
|
15
15
|
|
|
16
|
+
# Check if model is a LiteLLM model (has provider prefix)
|
|
17
|
+
if "/" in model and any(
|
|
18
|
+
model.startswith(prefix) for prefix in ["openai/", "anthropic/", "google/", "ollama/"]
|
|
19
|
+
):
|
|
20
|
+
return 'litellm'
|
|
21
|
+
|
|
16
22
|
model_lower = model.lower()
|
|
17
23
|
|
|
18
24
|
if model_lower.startswith('claude'):
|
|
@@ -25,32 +31,4 @@ def get_model_provider(model: str) -> Union[str, None]:
|
|
|
25
31
|
return 'openai'
|
|
26
32
|
|
|
27
33
|
return None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def does_message_require_fast_model(message_type: MessageType) -> bool:
|
|
31
|
-
"""
|
|
32
|
-
Determines if a message requires the fast model.
|
|
33
|
-
|
|
34
|
-
The fast model is used for messages that are not chat messages.
|
|
35
|
-
For example, inline completions and chat name generation need to be fast
|
|
36
|
-
so they don't slow down the user's experience.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
if message_type in (
|
|
40
|
-
MessageType.CHAT,
|
|
41
|
-
MessageType.SMART_DEBUG,
|
|
42
|
-
MessageType.CODE_EXPLAIN,
|
|
43
|
-
MessageType.AGENT_EXECUTION,
|
|
44
|
-
MessageType.AGENT_SCRATCHPAD_RESULT,
|
|
45
|
-
MessageType.AGENT_AUTO_ERROR_FIXUP,
|
|
46
|
-
):
|
|
47
|
-
return False
|
|
48
|
-
elif message_type in (MessageType.INLINE_COMPLETION, MessageType.CHAT_NAME_GENERATION):
|
|
49
|
-
return True
|
|
50
|
-
elif message_type in (MessageType.START_NEW_CHAT, MessageType.FETCH_HISTORY, MessageType.GET_THREADS, MessageType.DELETE_THREAD, MessageType.UPDATE_MODEL_CONFIG):
|
|
51
|
-
# These messages don't use any model, but we add them here for type safety
|
|
52
|
-
return True
|
|
53
|
-
else:
|
|
54
|
-
raise ValueError(f"Invalid message type: {message_type}")
|
|
55
|
-
|
|
56
34
|
|