mito-ai 0.1.33__py3-none-any.whl → 0.1.49__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 +49 -9
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +142 -67
- mito_ai/{app_builder → app_deploy}/__init__.py +1 -1
- mito_ai/app_deploy/app_deploy_utils.py +44 -0
- mito_ai/app_deploy/handlers.py +345 -0
- mito_ai/{app_builder → app_deploy}/models.py +35 -22
- mito_ai/app_manager/__init__.py +4 -0
- mito_ai/app_manager/handlers.py +167 -0
- mito_ai/app_manager/models.py +71 -0
- mito_ai/app_manager/utils.py +24 -0
- mito_ai/auth/README.md +18 -0
- mito_ai/auth/__init__.py +6 -0
- mito_ai/auth/handlers.py +96 -0
- mito_ai/auth/urls.py +13 -0
- mito_ai/chat_history/handlers.py +63 -0
- mito_ai/chat_history/urls.py +32 -0
- mito_ai/completions/completion_handlers/agent_execution_handler.py +1 -1
- mito_ai/completions/completion_handlers/chat_completion_handler.py +4 -4
- mito_ai/completions/completion_handlers/utils.py +99 -37
- mito_ai/completions/handlers.py +57 -20
- mito_ai/completions/message_history.py +9 -1
- mito_ai/completions/models.py +31 -7
- mito_ai/completions/prompt_builders/agent_execution_prompt.py +21 -2
- mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +8 -0
- mito_ai/completions/prompt_builders/agent_system_message.py +115 -42
- mito_ai/completions/prompt_builders/chat_name_prompt.py +6 -6
- mito_ai/completions/prompt_builders/chat_prompt.py +18 -11
- mito_ai/completions/prompt_builders/chat_system_message.py +4 -0
- mito_ai/completions/prompt_builders/prompt_constants.py +23 -4
- mito_ai/completions/prompt_builders/utils.py +72 -10
- mito_ai/completions/providers.py +81 -47
- mito_ai/constants.py +25 -24
- mito_ai/file_uploads/__init__.py +3 -0
- mito_ai/file_uploads/handlers.py +248 -0
- mito_ai/file_uploads/urls.py +21 -0
- mito_ai/gemini_client.py +44 -48
- mito_ai/log/handlers.py +10 -3
- mito_ai/log/urls.py +3 -3
- mito_ai/openai_client.py +30 -44
- mito_ai/path_utils.py +70 -0
- mito_ai/streamlit_conversion/agent_utils.py +37 -0
- mito_ai/streamlit_conversion/prompts/prompt_constants.py +172 -0
- mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
- mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +46 -0
- mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
- mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +45 -0
- mito_ai/streamlit_conversion/prompts/streamlit_system_prompt.py +56 -0
- mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
- mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +144 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +85 -0
- mito_ai/streamlit_conversion/validate_streamlit_app.py +105 -0
- mito_ai/streamlit_preview/__init__.py +6 -0
- mito_ai/streamlit_preview/handlers.py +111 -0
- mito_ai/streamlit_preview/manager.py +152 -0
- mito_ai/streamlit_preview/urls.py +22 -0
- mito_ai/streamlit_preview/utils.py +29 -0
- mito_ai/tests/chat_history/test_chat_history.py +211 -0
- mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
- mito_ai/tests/deploy_app/test_app_deploy_utils.py +89 -0
- mito_ai/tests/file_uploads/__init__.py +2 -0
- mito_ai/tests/file_uploads/test_handlers.py +282 -0
- mito_ai/tests/message_history/test_generate_short_chat_name.py +0 -4
- mito_ai/tests/message_history/test_message_history_utils.py +103 -23
- mito_ai/tests/open_ai_utils_test.py +18 -22
- mito_ai/tests/providers/test_anthropic_client.py +447 -0
- mito_ai/tests/providers/test_azure.py +2 -6
- mito_ai/tests/providers/test_capabilities.py +120 -0
- mito_ai/tests/{test_gemini_client.py → providers/test_gemini_client.py} +40 -36
- mito_ai/tests/providers/test_mito_server_utils.py +448 -0
- mito_ai/tests/providers/test_model_resolution.py +130 -0
- mito_ai/tests/providers/test_openai_client.py +57 -0
- mito_ai/tests/providers/test_provider_completion_exception.py +66 -0
- mito_ai/tests/providers/test_provider_limits.py +42 -0
- mito_ai/tests/providers/test_providers.py +382 -0
- mito_ai/tests/providers/test_retry_logic.py +389 -0
- mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
- mito_ai/tests/providers/utils.py +85 -0
- mito_ai/tests/streamlit_conversion/__init__.py +3 -0
- mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +246 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +193 -0
- mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +112 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +118 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +292 -0
- mito_ai/tests/test_constants.py +31 -3
- mito_ai/tests/test_telemetry.py +12 -0
- mito_ai/tests/user/__init__.py +2 -0
- mito_ai/tests/user/test_user.py +120 -0
- mito_ai/tests/utils/test_anthropic_utils.py +6 -6
- mito_ai/user/handlers.py +45 -0
- mito_ai/user/urls.py +21 -0
- mito_ai/utils/anthropic_utils.py +55 -121
- mito_ai/utils/create.py +17 -1
- mito_ai/utils/error_classes.py +42 -0
- mito_ai/utils/gemini_utils.py +39 -94
- mito_ai/utils/message_history_utils.py +7 -4
- mito_ai/utils/mito_server_utils.py +242 -0
- mito_ai/utils/open_ai_utils.py +38 -155
- mito_ai/utils/provider_utils.py +49 -0
- mito_ai/utils/server_limits.py +1 -1
- mito_ai/utils/telemetry_utils.py +137 -5
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +102 -100
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/package.json +4 -2
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +3 -1
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +2 -2
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +15948 -8403
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js +58 -33
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js.map +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +10 -2
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
- mito_ai-0.1.49.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 +533 -0
- mito_ai-0.1.49.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 +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +6941 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +1021 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +59698 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
- mito_ai-0.1.49.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 +7440 -0
- mito_ai-0.1.49.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 +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/METADATA +5 -2
- mito_ai-0.1.49.dist-info/RECORD +205 -0
- mito_ai/app_builder/handlers.py +0 -218
- mito_ai/tests/providers_test.py +0 -438
- mito_ai/tests/test_anthropic_client.py +0 -270
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js +0 -7842
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
- mito_ai-0.1.33.dist-info/RECORD +0 -134
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.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.33.dist-info → mito_ai-0.1.49.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/licenses/LICENSE +0 -0
mito_ai/completions/providers.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
|
+
import asyncio
|
|
5
6
|
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
|
6
7
|
from mito_ai import constants
|
|
7
8
|
from openai.types.chat import ChatCompletionMessageParam
|
|
@@ -28,12 +29,16 @@ from mito_ai.completions.models import (
|
|
|
28
29
|
from mito_ai.utils.telemetry_utils import (
|
|
29
30
|
KEY_TYPE_PARAM,
|
|
30
31
|
MITO_AI_COMPLETION_ERROR,
|
|
32
|
+
MITO_AI_COMPLETION_RETRY,
|
|
31
33
|
MITO_SERVER_KEY,
|
|
32
34
|
USER_KEY,
|
|
33
35
|
log,
|
|
36
|
+
log_ai_completion_error,
|
|
37
|
+
log_ai_completion_retry,
|
|
34
38
|
log_ai_completion_success,
|
|
35
39
|
)
|
|
36
|
-
from mito_ai.
|
|
40
|
+
from mito_ai.utils.provider_utils import get_model_provider
|
|
41
|
+
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
37
42
|
|
|
38
43
|
__all__ = ["OpenAIProvider"]
|
|
39
44
|
|
|
@@ -66,6 +71,9 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
66
71
|
|
|
67
72
|
@property
|
|
68
73
|
def capabilities(self) -> AICapabilities:
|
|
74
|
+
"""
|
|
75
|
+
Returns the capabilities of the AI provider.
|
|
76
|
+
"""
|
|
69
77
|
if constants.CLAUDE_API_KEY and not self.api_key:
|
|
70
78
|
return AICapabilities(
|
|
71
79
|
configuration={"model": "<dynamic>"},
|
|
@@ -78,6 +86,7 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
78
86
|
)
|
|
79
87
|
if self._openai_client:
|
|
80
88
|
return self._openai_client.capabilities
|
|
89
|
+
|
|
81
90
|
return AICapabilities(
|
|
82
91
|
configuration={"model": "<dynamic>"},
|
|
83
92
|
provider="Mito server",
|
|
@@ -100,7 +109,8 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
100
109
|
model: str,
|
|
101
110
|
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
102
111
|
user_input: Optional[str] = None,
|
|
103
|
-
thread_id: Optional[str] = None
|
|
112
|
+
thread_id: Optional[str] = None,
|
|
113
|
+
max_retries: int = 3
|
|
104
114
|
) -> str:
|
|
105
115
|
"""
|
|
106
116
|
Request completions from the AI provider.
|
|
@@ -109,43 +119,69 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
109
119
|
completion = None
|
|
110
120
|
last_message_content = str(messages[-1].get('content', '')) if messages else ""
|
|
111
121
|
model_type = get_model_provider(model)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
|
|
123
|
+
# Retry loop
|
|
124
|
+
for attempt in range(max_retries + 1):
|
|
125
|
+
try:
|
|
126
|
+
if model_type == "claude":
|
|
127
|
+
api_key = constants.CLAUDE_API_KEY
|
|
128
|
+
anthropic_client = AnthropicClient(api_key=api_key)
|
|
129
|
+
completion = await anthropic_client.request_completions(messages, model, response_format_info, message_type)
|
|
130
|
+
elif model_type == "gemini":
|
|
131
|
+
api_key = constants.GEMINI_API_KEY
|
|
132
|
+
gemini_client = GeminiClient(api_key=api_key)
|
|
133
|
+
messages_for_gemini = [dict(m) for m in messages]
|
|
134
|
+
completion = await gemini_client.request_completions(messages_for_gemini, model, response_format_info, message_type)
|
|
135
|
+
elif model_type == "openai":
|
|
136
|
+
if not self._openai_client:
|
|
137
|
+
raise RuntimeError("OpenAI client is not initialized.")
|
|
138
|
+
completion = await self._openai_client.request_completions(
|
|
139
|
+
message_type=message_type,
|
|
140
|
+
messages=messages,
|
|
141
|
+
model=model,
|
|
142
|
+
response_format_info=response_format_info
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"No AI provider configured for model: {model}")
|
|
146
|
+
|
|
147
|
+
# Success! Log and return
|
|
148
|
+
log_ai_completion_success(
|
|
149
|
+
key_type=USER_KEY if self.key_type == "user" else MITO_SERVER_KEY,
|
|
126
150
|
message_type=message_type,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
last_message_content=last_message_content,
|
|
152
|
+
response={"completion": completion},
|
|
153
|
+
user_input=user_input or "",
|
|
154
|
+
thread_id=thread_id or "",
|
|
155
|
+
model=model
|
|
130
156
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
thread_id=thread_id or "",
|
|
140
|
-
model=model
|
|
141
|
-
)
|
|
142
|
-
return completion
|
|
157
|
+
return completion # type: ignore
|
|
158
|
+
|
|
159
|
+
except PermissionError as e:
|
|
160
|
+
# If we hit a free tier limit, then raise an exception right away without retrying.
|
|
161
|
+
self.log.exception(f"Error during request_completions: {e}")
|
|
162
|
+
self.last_error = CompletionError.from_exception(e)
|
|
163
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
164
|
+
raise
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
except BaseException as e:
|
|
167
|
+
# Check if we should retry (not on the last attempt)
|
|
168
|
+
if attempt < max_retries:
|
|
169
|
+
# Exponential backoff: wait 2^attempt seconds
|
|
170
|
+
wait_time = 2 ** attempt
|
|
171
|
+
self.log.info(f"Retrying request_completions after {wait_time}s (attempt {attempt + 1}/{max_retries + 1}): {str(e)}")
|
|
172
|
+
log_ai_completion_retry('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
173
|
+
await asyncio.sleep(wait_time)
|
|
174
|
+
continue
|
|
175
|
+
else:
|
|
176
|
+
# Final failure after all retries - set error state and raise
|
|
177
|
+
self.log.exception(f"Error during request_completions after {attempt + 1} attempts: {e}")
|
|
178
|
+
self.last_error = CompletionError.from_exception(e)
|
|
179
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
# This should never be reached due to the raise in the except block,
|
|
183
|
+
# but added to satisfy the linter
|
|
184
|
+
raise RuntimeError("Unexpected code path in request_completions")
|
|
149
185
|
|
|
150
186
|
async def stream_completions(
|
|
151
187
|
self,
|
|
@@ -176,19 +212,23 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
176
212
|
try:
|
|
177
213
|
if model_type == "claude":
|
|
178
214
|
api_key = constants.CLAUDE_API_KEY
|
|
179
|
-
anthropic_client = AnthropicClient(api_key=api_key
|
|
180
|
-
accumulated_response = await anthropic_client.
|
|
215
|
+
anthropic_client = AnthropicClient(api_key=api_key)
|
|
216
|
+
accumulated_response = await anthropic_client.stream_completions(
|
|
181
217
|
messages=messages,
|
|
218
|
+
model=model,
|
|
182
219
|
message_type=message_type,
|
|
183
220
|
message_id=message_id,
|
|
184
221
|
reply_fn=reply_fn
|
|
185
222
|
)
|
|
186
223
|
elif model_type == "gemini":
|
|
187
224
|
api_key = constants.GEMINI_API_KEY
|
|
188
|
-
gemini_client = GeminiClient(api_key=api_key
|
|
225
|
+
gemini_client = GeminiClient(api_key=api_key)
|
|
226
|
+
# TODO: We shouldn't need to do this because the messages should already be dictionaries...
|
|
227
|
+
# but if we do have to do some pre-processing, we should do it in the gemini_client instead.
|
|
189
228
|
messages_for_gemini = [dict(m) for m in messages]
|
|
190
229
|
accumulated_response = await gemini_client.stream_completions(
|
|
191
230
|
messages=messages_for_gemini,
|
|
231
|
+
model=model,
|
|
192
232
|
message_id=message_id,
|
|
193
233
|
reply_fn=reply_fn,
|
|
194
234
|
message_type=message_type
|
|
@@ -224,14 +264,8 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
224
264
|
except BaseException as e:
|
|
225
265
|
self.log.exception(f"Error during stream_completions: {e}")
|
|
226
266
|
self.last_error = CompletionError.from_exception(e)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
params={
|
|
230
|
-
KEY_TYPE_PARAM: self.key_type,
|
|
231
|
-
'message_type': message_type.value,
|
|
232
|
-
},
|
|
233
|
-
error=e
|
|
234
|
-
)
|
|
267
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id, message_type, e)
|
|
268
|
+
|
|
235
269
|
# Send error message to client before raising
|
|
236
270
|
reply_fn(CompletionStreamChunk(
|
|
237
271
|
parent_id=message_id,
|
mito_ai/constants.py
CHANGED
|
@@ -23,30 +23,10 @@ AZURE_OPENAI_API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION")
|
|
|
23
23
|
AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT")
|
|
24
24
|
AZURE_OPENAI_MODEL = os.environ.get("AZURE_OPENAI_MODEL")
|
|
25
25
|
|
|
26
|
-
def get_model_provider(model: str) -> Union[str, None]:
|
|
27
|
-
"""
|
|
28
|
-
Determine the model type based on the model name prefix
|
|
29
|
-
"""
|
|
30
|
-
if not model:
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
model_lower = model.lower()
|
|
34
|
-
|
|
35
|
-
if model_lower.startswith('claude'):
|
|
36
|
-
return 'claude'
|
|
37
|
-
elif model_lower.startswith('gemini'):
|
|
38
|
-
return 'gemini'
|
|
39
|
-
elif model_lower.startswith('ollama'):
|
|
40
|
-
return 'ollama'
|
|
41
|
-
elif model_lower.startswith('gpt'):
|
|
42
|
-
return 'openai'
|
|
43
|
-
|
|
44
|
-
return None
|
|
45
|
-
|
|
46
|
-
|
|
47
26
|
# Mito AI Base URLs and Endpoint Paths
|
|
48
|
-
MITO_PROD_BASE_URL = "https://
|
|
49
|
-
MITO_DEV_BASE_URL = "https://
|
|
27
|
+
MITO_PROD_BASE_URL = "https://7eax4i53f5odkshhlry4gw23by0yvnuv.lambda-url.us-east-1.on.aws/v2"
|
|
28
|
+
MITO_DEV_BASE_URL = "https://g5vwmogjg7gh7aktqezyrvcq6a0hyfnr.lambda-url.us-east-1.on.aws/v2"
|
|
29
|
+
MITO_LOCAL_BASE_URL = "http://127.0.0.1:8000/v2" # When you are running the mito completion server locally
|
|
50
30
|
|
|
51
31
|
# Set ACTIVE_BASE_URL manually
|
|
52
32
|
ACTIVE_BASE_URL = MITO_PROD_BASE_URL # Change to MITO_DEV_BASE_URL for dev
|
|
@@ -59,4 +39,25 @@ OPENAI_PATH = "openai/completions"
|
|
|
59
39
|
# Full URLs (always use ACTIVE_BASE_URL)
|
|
60
40
|
MITO_ANTHROPIC_URL = f"{ACTIVE_BASE_URL}/{ANTHROPIC_PATH}"
|
|
61
41
|
MITO_GEMINI_URL = f"{ACTIVE_BASE_URL}/{GEMINI_PATH}"
|
|
62
|
-
MITO_OPENAI_URL = f"{ACTIVE_BASE_URL}/{OPENAI_PATH}"
|
|
42
|
+
MITO_OPENAI_URL = f"{ACTIVE_BASE_URL}/{OPENAI_PATH}"
|
|
43
|
+
|
|
44
|
+
# Streamlit conversion endpoints
|
|
45
|
+
MITO_STREAMLIT_DEV_BASE_URL = "https://fr12uvtfy5.execute-api.us-east-1.amazonaws.com"
|
|
46
|
+
MITO_STREAMLIT_TEST_BASE_URL = "https://iyual08t6d.execute-api.us-east-1.amazonaws.com"
|
|
47
|
+
|
|
48
|
+
# Set ACTIVE_BASE_URL manually
|
|
49
|
+
# TODO: Modify to PROD url before release
|
|
50
|
+
ACTIVE_STREAMLIT_BASE_URL = MITO_STREAMLIT_DEV_BASE_URL # Change to MITO_STREAMLIT_DEV_BASE_URL for dev
|
|
51
|
+
|
|
52
|
+
# AWS Cognito configuration
|
|
53
|
+
COGNITO_CONFIG_DEV = {
|
|
54
|
+
'TOKEN_ENDPOINT': 'https://mito-app-auth.auth.us-east-1.amazoncognito.com/oauth2/token',
|
|
55
|
+
'CLIENT_ID': '6ara3u3l8sss738hrhbq1qtiqf',
|
|
56
|
+
'CLIENT_SECRET': '',
|
|
57
|
+
'REDIRECT_URI': 'http://localhost:8888/lab'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ACTIVE_COGNITO_CONFIG = COGNITO_CONFIG_DEV # Change to COGNITO_CONFIG_DEV for dev
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
MESSAGE_HISTORY_TRIM_THRESHOLD: int = 3
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import tornado
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from jupyter_server.base.handlers import APIHandler
|
|
9
|
+
from mito_ai.utils.telemetry_utils import (
|
|
10
|
+
log_file_upload_attempt,
|
|
11
|
+
log_file_upload_failure,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
MAX_IMAGE_SIZE_MB = 3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_image_file(filename: str) -> bool:
|
|
18
|
+
image_extensions = {
|
|
19
|
+
".jpg",
|
|
20
|
+
".jpeg",
|
|
21
|
+
".png",
|
|
22
|
+
".gif",
|
|
23
|
+
".bmp",
|
|
24
|
+
".tiff",
|
|
25
|
+
".tif",
|
|
26
|
+
".webp",
|
|
27
|
+
".svg",
|
|
28
|
+
}
|
|
29
|
+
file_extension = os.path.splitext(filename)[1].lower()
|
|
30
|
+
return file_extension in image_extensions
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _check_image_size_limit(file_data: bytes, filename: str) -> None:
|
|
34
|
+
if not _is_image_file(filename):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
file_size_mb = len(file_data) / (1024 * 1024) # Convert bytes to MB
|
|
38
|
+
|
|
39
|
+
if file_size_mb > MAX_IMAGE_SIZE_MB:
|
|
40
|
+
raise ValueError(f"Image exceeded {MAX_IMAGE_SIZE_MB}MB limit.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FileUploadHandler(APIHandler):
|
|
44
|
+
# Class-level dictionary to store temporary directories for each file upload
|
|
45
|
+
# This persists across handler instances since Tornado recreates handlers per request
|
|
46
|
+
# Key: filename, Value: dict with temp_dir, total_chunks, received_chunks, logged_upload
|
|
47
|
+
_temp_dirs: Dict[str, Dict[str, Any]] = {}
|
|
48
|
+
|
|
49
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
50
|
+
super().__init__(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
@tornado.web.authenticated
|
|
53
|
+
def post(self) -> None:
|
|
54
|
+
"""Handle file upload with multipart form data."""
|
|
55
|
+
try:
|
|
56
|
+
# Validate request has file
|
|
57
|
+
if not self._validate_file_upload():
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
uploaded_file = self.request.files["file"][0]
|
|
61
|
+
filename = uploaded_file["filename"]
|
|
62
|
+
file_data = uploaded_file["body"]
|
|
63
|
+
|
|
64
|
+
# Get notebook directory from request
|
|
65
|
+
notebook_dir = self.get_argument("notebook_dir", ".")
|
|
66
|
+
|
|
67
|
+
# Check if this is a chunked upload
|
|
68
|
+
chunk_number = self.get_argument("chunk_number", None)
|
|
69
|
+
total_chunks = self.get_argument("total_chunks", None)
|
|
70
|
+
|
|
71
|
+
if chunk_number and total_chunks:
|
|
72
|
+
self._handle_chunked_upload(
|
|
73
|
+
filename, file_data, chunk_number, total_chunks, notebook_dir
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
# Log the file upload attempt for regular (non-chunked) uploads
|
|
77
|
+
file_extension = filename.split(".")[-1].lower()
|
|
78
|
+
log_file_upload_attempt(filename, file_extension, False, 0)
|
|
79
|
+
self._handle_regular_upload(filename, file_data, notebook_dir)
|
|
80
|
+
|
|
81
|
+
self.finish()
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self._handle_error(str(e))
|
|
85
|
+
|
|
86
|
+
def _validate_file_upload(self) -> bool:
|
|
87
|
+
"""Validate that a file was uploaded in the request."""
|
|
88
|
+
if "file" not in self.request.files:
|
|
89
|
+
self._handle_error("No file uploaded", status_code=400)
|
|
90
|
+
return False
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def _handle_chunked_upload(
|
|
94
|
+
self,
|
|
95
|
+
filename: str,
|
|
96
|
+
file_data: bytes,
|
|
97
|
+
chunk_number: str,
|
|
98
|
+
total_chunks: str,
|
|
99
|
+
notebook_dir: str,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Handle chunked file upload."""
|
|
102
|
+
chunk_num = int(chunk_number)
|
|
103
|
+
total_chunks_num = int(total_chunks)
|
|
104
|
+
|
|
105
|
+
# Log the file upload attempt only for the first chunk
|
|
106
|
+
if chunk_num == 1:
|
|
107
|
+
file_extension = filename.split(".")[-1].lower()
|
|
108
|
+
log_file_upload_attempt(filename, file_extension, True, total_chunks_num)
|
|
109
|
+
|
|
110
|
+
# Save chunk to temporary file
|
|
111
|
+
self._save_chunk(filename, file_data, chunk_num, total_chunks_num)
|
|
112
|
+
|
|
113
|
+
# Check if all chunks are received and reconstruct if complete
|
|
114
|
+
if self._are_all_chunks_received(filename, total_chunks_num):
|
|
115
|
+
self._reconstruct_file(filename, total_chunks_num, notebook_dir)
|
|
116
|
+
self._send_chunk_complete_response(filename, notebook_dir)
|
|
117
|
+
else:
|
|
118
|
+
self._send_chunk_received_response(chunk_num, total_chunks_num)
|
|
119
|
+
|
|
120
|
+
def _handle_regular_upload(
|
|
121
|
+
self, filename: str, file_data: bytes, notebook_dir: str
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Handle regular (non-chunked) file upload."""
|
|
124
|
+
# Check image file size limit before saving
|
|
125
|
+
_check_image_size_limit(file_data, filename)
|
|
126
|
+
|
|
127
|
+
file_path = os.path.join(notebook_dir, filename)
|
|
128
|
+
with open(file_path, "wb") as f:
|
|
129
|
+
f.write(file_data)
|
|
130
|
+
|
|
131
|
+
self.write({"success": True, "filename": filename, "path": file_path})
|
|
132
|
+
|
|
133
|
+
def _save_chunk(
|
|
134
|
+
self, filename: str, file_data: bytes, chunk_number: int, total_chunks: int
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Save a chunk to a temporary file."""
|
|
137
|
+
# Initialize temporary directory for this file if it doesn't exist
|
|
138
|
+
if filename not in self._temp_dirs:
|
|
139
|
+
temp_dir = tempfile.mkdtemp(prefix=f"mito_upload_{filename}_")
|
|
140
|
+
self._temp_dirs[filename] = {
|
|
141
|
+
"temp_dir": temp_dir,
|
|
142
|
+
"total_chunks": total_chunks,
|
|
143
|
+
"received_chunks": set(),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Save the chunk to the temporary directory
|
|
147
|
+
chunk_filename = os.path.join(
|
|
148
|
+
self._temp_dirs[filename]["temp_dir"], f"chunk_{chunk_number}"
|
|
149
|
+
)
|
|
150
|
+
with open(chunk_filename, "wb") as f:
|
|
151
|
+
f.write(file_data)
|
|
152
|
+
|
|
153
|
+
# Mark this chunk as received
|
|
154
|
+
self._temp_dirs[filename]["received_chunks"].add(chunk_number)
|
|
155
|
+
|
|
156
|
+
def _are_all_chunks_received(self, filename: str, total_chunks: int) -> bool:
|
|
157
|
+
"""Check if all chunks for a file have been received."""
|
|
158
|
+
if filename not in self._temp_dirs:
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
received_chunks = self._temp_dirs[filename]["received_chunks"]
|
|
162
|
+
is_complete = len(received_chunks) == total_chunks
|
|
163
|
+
return is_complete
|
|
164
|
+
|
|
165
|
+
def _reconstruct_file(
|
|
166
|
+
self, filename: str, total_chunks: int, notebook_dir: str
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Reconstruct the final file from all chunks and clean up temporary directory."""
|
|
169
|
+
|
|
170
|
+
if filename not in self._temp_dirs:
|
|
171
|
+
raise ValueError(f"No temporary directory found for file: {filename}")
|
|
172
|
+
|
|
173
|
+
temp_dir = self._temp_dirs[filename]["temp_dir"]
|
|
174
|
+
file_path = os.path.join(notebook_dir, filename)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
# First, read all chunks to check total file size for images
|
|
178
|
+
all_file_data = b""
|
|
179
|
+
for i in range(1, total_chunks + 1):
|
|
180
|
+
chunk_filename = os.path.join(temp_dir, f"chunk_{i}")
|
|
181
|
+
with open(chunk_filename, "rb") as chunk_file:
|
|
182
|
+
chunk_data = chunk_file.read()
|
|
183
|
+
all_file_data += chunk_data
|
|
184
|
+
|
|
185
|
+
# Check image file size limit before saving
|
|
186
|
+
_check_image_size_limit(all_file_data, filename)
|
|
187
|
+
|
|
188
|
+
# Write the complete file
|
|
189
|
+
with open(file_path, "wb") as final_file:
|
|
190
|
+
final_file.write(all_file_data)
|
|
191
|
+
finally:
|
|
192
|
+
# Clean up the temporary directory
|
|
193
|
+
self._cleanup_temp_dir(filename)
|
|
194
|
+
|
|
195
|
+
def _cleanup_temp_dir(self, filename: str) -> None:
|
|
196
|
+
"""Clean up the temporary directory for a file."""
|
|
197
|
+
if filename in self._temp_dirs:
|
|
198
|
+
temp_dir = self._temp_dirs[filename]["temp_dir"]
|
|
199
|
+
try:
|
|
200
|
+
import shutil
|
|
201
|
+
|
|
202
|
+
shutil.rmtree(temp_dir)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
# Log the error but don't fail the upload
|
|
205
|
+
print(
|
|
206
|
+
f"Warning: Failed to clean up temporary directory {temp_dir}: {e}"
|
|
207
|
+
)
|
|
208
|
+
finally:
|
|
209
|
+
# Remove from tracking dictionary
|
|
210
|
+
del self._temp_dirs[filename]
|
|
211
|
+
|
|
212
|
+
def _send_chunk_complete_response(self, filename: str, notebook_dir: str) -> None:
|
|
213
|
+
"""Send response indicating all chunks have been processed and file is complete."""
|
|
214
|
+
file_path = os.path.join(notebook_dir, filename)
|
|
215
|
+
self.write(
|
|
216
|
+
{
|
|
217
|
+
"success": True,
|
|
218
|
+
"filename": filename,
|
|
219
|
+
"path": file_path,
|
|
220
|
+
"chunk_complete": True,
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _send_chunk_received_response(
|
|
225
|
+
self, chunk_number: int, total_chunks: int
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Send response indicating a chunk was received but file is not yet complete."""
|
|
228
|
+
self.write(
|
|
229
|
+
{
|
|
230
|
+
"success": True,
|
|
231
|
+
"chunk_received": True,
|
|
232
|
+
"chunk_number": chunk_number,
|
|
233
|
+
"total_chunks": total_chunks,
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def _handle_error(self, error_message: str, status_code: int = 500) -> None:
|
|
238
|
+
"""Handle errors and send appropriate error response."""
|
|
239
|
+
log_file_upload_failure(error_message)
|
|
240
|
+
self.set_status(status_code)
|
|
241
|
+
self.write({"error": error_message})
|
|
242
|
+
self.finish()
|
|
243
|
+
|
|
244
|
+
def on_finish(self) -> None:
|
|
245
|
+
"""Clean up any remaining temporary directories when the handler is finished."""
|
|
246
|
+
super().on_finish()
|
|
247
|
+
# Note: We don't clean up here anymore since we want to preserve state across requests
|
|
248
|
+
# The cleanup happens when the file is fully reconstructed
|
|
@@ -0,0 +1,21 @@
|
|
|
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, Any
|
|
5
|
+
from jupyter_server.utils import url_path_join
|
|
6
|
+
from mito_ai.file_uploads.handlers import FileUploadHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_file_uploads_urls(base_url: str) -> List[Tuple[str, Any, dict]]:
|
|
10
|
+
"""Get all file uploads related URL patterns.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
base_url: The base URL for the Jupyter server
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
List of (url_pattern, handler_class, handler_kwargs) tuples
|
|
17
|
+
"""
|
|
18
|
+
BASE_URL = base_url + "/mito-ai"
|
|
19
|
+
return [
|
|
20
|
+
(url_path_join(BASE_URL, "upload"), FileUploadHandler, {}),
|
|
21
|
+
]
|
mito_ai/gemini_client.py
CHANGED
|
@@ -5,10 +5,9 @@ from typing import Any, Callable, Dict, List, Optional, Union, Tuple
|
|
|
5
5
|
from google import genai
|
|
6
6
|
from google.genai import types
|
|
7
7
|
from google.genai.types import GenerateContentConfig, Part, Content, GenerateContentResponse
|
|
8
|
-
from mito_ai.completions.models import CompletionItem, CompletionReply, CompletionStreamChunk, MessageType, ResponseFormatInfo
|
|
8
|
+
from mito_ai.completions.models import CompletionError, CompletionItem, CompletionReply, CompletionStreamChunk, MessageType, ResponseFormatInfo
|
|
9
9
|
from mito_ai.utils.gemini_utils import get_gemini_completion_from_mito_server, stream_gemini_completion_from_mito_server, get_gemini_completion_function_params
|
|
10
|
-
|
|
11
|
-
GEMINI_FAST_MODEL = "gemini-2.0-flash-lite"
|
|
10
|
+
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
12
11
|
|
|
13
12
|
def extract_and_parse_gemini_json_response(response: GenerateContentResponse) -> Optional[str]:
|
|
14
13
|
"""
|
|
@@ -100,65 +99,62 @@ def get_gemini_system_prompt_and_messages(messages: List[Dict[str, Any]]) -> Tup
|
|
|
100
99
|
|
|
101
100
|
|
|
102
101
|
class GeminiClient:
|
|
103
|
-
def __init__(self, api_key: Optional[str]
|
|
102
|
+
def __init__(self, api_key: Optional[str]):
|
|
104
103
|
self.api_key = api_key
|
|
105
|
-
self.model = model
|
|
106
104
|
if api_key:
|
|
107
105
|
self.client = genai.Client(api_key=api_key)
|
|
108
106
|
|
|
109
107
|
async def request_completions(
|
|
110
108
|
self,
|
|
111
109
|
messages: List[Dict[str, Any]],
|
|
110
|
+
model: str,
|
|
112
111
|
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
113
112
|
message_type: MessageType = MessageType.CHAT
|
|
114
113
|
) -> str:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
# Extract system instructions and contents
|
|
115
|
+
system_instructions, contents = get_gemini_system_prompt_and_messages(messages)
|
|
116
|
+
|
|
117
|
+
# Get provider data for Gemini completion
|
|
118
|
+
provider_data = get_gemini_completion_function_params(
|
|
119
|
+
model=model,
|
|
120
|
+
contents=contents,
|
|
121
|
+
message_type=message_type,
|
|
122
|
+
response_format_info=response_format_info
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if self.api_key:
|
|
126
|
+
# Generate content using the Gemini client
|
|
127
|
+
response_config = GenerateContentConfig(
|
|
128
|
+
system_instruction=system_instructions,
|
|
129
|
+
response_mime_type=provider_data.get("config", {}).get("response_mime_type"),
|
|
130
|
+
response_schema=provider_data.get("config", {}).get("response_schema")
|
|
131
|
+
)
|
|
132
|
+
response = self.client.models.generate_content(
|
|
133
|
+
model=provider_data["model"],
|
|
134
|
+
contents=contents, # type: ignore
|
|
135
|
+
config=response_config
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
result = extract_and_parse_gemini_json_response(response)
|
|
139
|
+
|
|
140
|
+
if not result:
|
|
141
|
+
return "No response received from Gemini API"
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
else:
|
|
145
|
+
# Fallback to Mito server for completion
|
|
146
|
+
return await get_gemini_completion_from_mito_server(
|
|
147
|
+
model=provider_data["model"],
|
|
148
|
+
contents=messages, # Use the extracted contents instead of converted messages to avoid serialization issues
|
|
123
149
|
message_type=message_type,
|
|
124
|
-
|
|
150
|
+
config=provider_data.get("config", None),
|
|
151
|
+
response_format_info=response_format_info,
|
|
125
152
|
)
|
|
126
153
|
|
|
127
|
-
if self.api_key:
|
|
128
|
-
# Generate content using the Gemini client
|
|
129
|
-
response_config = GenerateContentConfig(
|
|
130
|
-
system_instruction=system_instructions,
|
|
131
|
-
response_mime_type=provider_data.get("config", {}).get("response_mime_type"),
|
|
132
|
-
response_schema=provider_data.get("config", {}).get("response_schema")
|
|
133
|
-
)
|
|
134
|
-
response = self.client.models.generate_content(
|
|
135
|
-
model=provider_data["model"],
|
|
136
|
-
contents=contents, # type: ignore
|
|
137
|
-
config=response_config
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
result = extract_and_parse_gemini_json_response(response)
|
|
141
|
-
|
|
142
|
-
if not result:
|
|
143
|
-
return "No response received from Gemini API"
|
|
144
|
-
|
|
145
|
-
return result
|
|
146
|
-
else:
|
|
147
|
-
# Fallback to Mito server for completion
|
|
148
|
-
return await get_gemini_completion_from_mito_server(
|
|
149
|
-
model=provider_data["model"],
|
|
150
|
-
contents=messages, # Use the extracted contents instead of converted messages to avoid serialization issues
|
|
151
|
-
message_type=message_type,
|
|
152
|
-
config=provider_data.get("config", None),
|
|
153
|
-
response_format_info=response_format_info,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
except Exception as e:
|
|
157
|
-
return f"Error generating content: {str(e)}"
|
|
158
|
-
|
|
159
154
|
async def stream_completions(
|
|
160
155
|
self,
|
|
161
156
|
messages: List[Dict[str, Any]],
|
|
157
|
+
model: str,
|
|
162
158
|
message_id: str,
|
|
163
159
|
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None],
|
|
164
160
|
message_type: MessageType = MessageType.CHAT
|
|
@@ -169,7 +165,7 @@ class GeminiClient:
|
|
|
169
165
|
system_instructions, contents = get_gemini_system_prompt_and_messages(messages)
|
|
170
166
|
if self.api_key:
|
|
171
167
|
for chunk in self.client.models.generate_content_stream(
|
|
172
|
-
model=
|
|
168
|
+
model=model,
|
|
173
169
|
contents=contents, # type: ignore
|
|
174
170
|
config=GenerateContentConfig(
|
|
175
171
|
system_instruction=system_instructions
|
|
@@ -208,7 +204,7 @@ class GeminiClient:
|
|
|
208
204
|
return accumulated_response
|
|
209
205
|
else:
|
|
210
206
|
async for chunk_text in stream_gemini_completion_from_mito_server(
|
|
211
|
-
model=
|
|
207
|
+
model=model,
|
|
212
208
|
contents=messages, # Use the extracted contents instead of converted messages to avoid serialization issues
|
|
213
209
|
message_type=message_type,
|
|
214
210
|
message_id=message_id,
|