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/utils/gemini_utils.py
CHANGED
|
@@ -5,14 +5,17 @@ import asyncio
|
|
|
5
5
|
import json
|
|
6
6
|
import time
|
|
7
7
|
from typing import Any, Dict, List, Optional, Callable, Union, AsyncGenerator, Tuple
|
|
8
|
-
from
|
|
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
|
-
from .utils import _create_http_client
|
|
11
10
|
from mito_ai.constants import MITO_GEMINI_URL
|
|
11
|
+
from mito_ai.utils.provider_utils import does_message_require_fast_model
|
|
12
|
+
from mito_ai.utils.utils import _create_http_client
|
|
12
13
|
|
|
13
14
|
timeout = 30
|
|
14
15
|
max_retries = 1
|
|
15
16
|
|
|
17
|
+
FAST_GEMINI_MODEL = "gemini-2.0-flash-lite"
|
|
18
|
+
|
|
16
19
|
def _prepare_gemini_request_data_and_headers(
|
|
17
20
|
model: str,
|
|
18
21
|
contents: List[Dict[str, Any]],
|
|
@@ -62,116 +65,58 @@ async def get_gemini_completion_from_mito_server(
|
|
|
62
65
|
response_format_info: Optional[Any] = None
|
|
63
66
|
) -> str:
|
|
64
67
|
data, headers = _prepare_gemini_request_data_and_headers(model, contents, message_type, config, response_format_info, stream=False)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
print(f"Gemini request completed in {time.time() - start_time:.2f} seconds")
|
|
76
|
-
except Exception as e:
|
|
77
|
-
print(f"Gemini request failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
|
|
78
|
-
raise
|
|
79
|
-
finally:
|
|
80
|
-
http_client.close()
|
|
81
|
-
|
|
82
|
-
# The response is a string
|
|
83
|
-
return res.body.decode("utf-8")
|
|
68
|
+
return await get_response_from_mito_server(
|
|
69
|
+
MITO_GEMINI_URL,
|
|
70
|
+
headers,
|
|
71
|
+
data,
|
|
72
|
+
timeout,
|
|
73
|
+
max_retries,
|
|
74
|
+
message_type,
|
|
75
|
+
provider_name="Gemini"
|
|
76
|
+
)
|
|
84
77
|
|
|
85
78
|
async def stream_gemini_completion_from_mito_server(
|
|
86
79
|
model: str,
|
|
87
80
|
contents: List[Dict[str, Any]],
|
|
88
81
|
message_type: MessageType,
|
|
89
82
|
message_id: str,
|
|
90
|
-
reply_fn:
|
|
83
|
+
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None]
|
|
91
84
|
) -> AsyncGenerator[str, None]:
|
|
92
85
|
data, headers = _prepare_gemini_request_data_and_headers(model, contents, message_type, stream=True)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async def wait_for_fetch() -> None:
|
|
114
|
-
try:
|
|
115
|
-
await fetch_future
|
|
116
|
-
nonlocal fetch_complete
|
|
117
|
-
fetch_complete = True
|
|
118
|
-
print("Gemini fetch completed")
|
|
119
|
-
except Exception as e:
|
|
120
|
-
print(f"Error in Gemini fetch: {str(e)}")
|
|
121
|
-
raise
|
|
122
|
-
fetch_task = asyncio.create_task(wait_for_fetch())
|
|
123
|
-
while not (fetch_complete and chunk_queue.empty()):
|
|
124
|
-
try:
|
|
125
|
-
chunk = await asyncio.wait_for(chunk_queue.get(), timeout=0.1)
|
|
126
|
-
clean_chunk = chunk.strip('"')
|
|
127
|
-
decoded_chunk = clean_chunk.encode().decode('unicode_escape')
|
|
128
|
-
if reply_fn and message_id:
|
|
129
|
-
reply_fn(CompletionStreamChunk(
|
|
130
|
-
parent_id=message_id,
|
|
131
|
-
chunk=CompletionItem(
|
|
132
|
-
content=decoded_chunk,
|
|
133
|
-
isIncomplete=True,
|
|
134
|
-
token=message_id,
|
|
135
|
-
),
|
|
136
|
-
done=False,
|
|
137
|
-
))
|
|
138
|
-
yield chunk
|
|
139
|
-
except asyncio.TimeoutError:
|
|
140
|
-
if fetch_complete and chunk_queue.empty():
|
|
141
|
-
break
|
|
142
|
-
continue
|
|
143
|
-
print(f"\nGemini stream completed in {time.time() - start_time:.2f} seconds")
|
|
144
|
-
if reply_fn and message_id:
|
|
145
|
-
reply_fn(CompletionStreamChunk(
|
|
146
|
-
parent_id=message_id,
|
|
147
|
-
chunk=CompletionItem(
|
|
148
|
-
content="",
|
|
149
|
-
isIncomplete=False,
|
|
150
|
-
token=message_id,
|
|
151
|
-
),
|
|
152
|
-
done=True,
|
|
153
|
-
))
|
|
154
|
-
except Exception as e:
|
|
155
|
-
print(f"\nGemini stream failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
|
|
156
|
-
if fetch_future:
|
|
157
|
-
try:
|
|
158
|
-
await fetch_future
|
|
159
|
-
except Exception:
|
|
160
|
-
pass
|
|
161
|
-
raise
|
|
162
|
-
finally:
|
|
163
|
-
http_client.close()
|
|
86
|
+
|
|
87
|
+
# Define chunk processor for Gemini's special processing
|
|
88
|
+
def gemini_chunk_processor(chunk: str) -> str:
|
|
89
|
+
clean_chunk = chunk.strip('"')
|
|
90
|
+
return clean_chunk.encode().decode('unicode_escape')
|
|
91
|
+
|
|
92
|
+
# Use the unified streaming function with Gemini's chunk processor
|
|
93
|
+
async for chunk in stream_response_from_mito_server(
|
|
94
|
+
url=MITO_GEMINI_URL,
|
|
95
|
+
headers=headers,
|
|
96
|
+
data=data,
|
|
97
|
+
timeout=timeout,
|
|
98
|
+
max_retries=max_retries,
|
|
99
|
+
message_type=message_type,
|
|
100
|
+
reply_fn=reply_fn,
|
|
101
|
+
message_id=message_id,
|
|
102
|
+
chunk_processor=gemini_chunk_processor,
|
|
103
|
+
provider_name="Gemini",
|
|
104
|
+
):
|
|
105
|
+
yield chunk
|
|
164
106
|
|
|
165
107
|
def get_gemini_completion_function_params(
|
|
108
|
+
message_type: MessageType,
|
|
166
109
|
model: str,
|
|
167
110
|
contents: list[dict[str, Any]],
|
|
168
|
-
message_type: MessageType,
|
|
169
111
|
response_format_info: Optional[Any] = None,
|
|
170
112
|
) -> Dict[str, Any]:
|
|
171
113
|
"""
|
|
172
114
|
Build the provider_data dict for Gemini completions, mirroring the OpenAI/Anthropic approach.
|
|
173
115
|
Only includes fields needed for the Gemini API.
|
|
174
116
|
"""
|
|
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
|
+
|
|
175
120
|
provider_data: Dict[str, Any] = {
|
|
176
121
|
"model": model,
|
|
177
122
|
"contents": contents,
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
5
|
from typing import List
|
|
6
|
+
from mito_ai.constants import MESSAGE_HISTORY_TRIM_THRESHOLD
|
|
6
7
|
from openai.types.chat import ChatCompletionMessageParam
|
|
7
8
|
from mito_ai.completions.prompt_builders.prompt_constants import (
|
|
8
9
|
ACTIVE_CELL_ID_SECTION_HEADING,
|
|
9
10
|
ACTIVE_CELL_OUTPUT_SECTION_HEADING,
|
|
10
11
|
GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING,
|
|
11
12
|
FILES_SECTION_HEADING,
|
|
13
|
+
STREAMLIT_APP_STATUS_SECTION_HEADING,
|
|
12
14
|
VARIABLES_SECTION_HEADING,
|
|
13
15
|
JUPYTER_NOTEBOOK_SECTION_HEADING,
|
|
14
16
|
CONTENT_REMOVED_PLACEHOLDER
|
|
@@ -30,7 +32,8 @@ def trim_sections_from_message_content(content: str) -> str:
|
|
|
30
32
|
JUPYTER_NOTEBOOK_SECTION_HEADING,
|
|
31
33
|
GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING,
|
|
32
34
|
ACTIVE_CELL_OUTPUT_SECTION_HEADING,
|
|
33
|
-
ACTIVE_CELL_ID_SECTION_HEADING
|
|
35
|
+
ACTIVE_CELL_ID_SECTION_HEADING,
|
|
36
|
+
STREAMLIT_APP_STATUS_SECTION_HEADING
|
|
34
37
|
]
|
|
35
38
|
|
|
36
39
|
for heading in section_headings:
|
|
@@ -43,18 +46,18 @@ def trim_sections_from_message_content(content: str) -> str:
|
|
|
43
46
|
return content
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def trim_old_messages(messages: List[ChatCompletionMessageParam]
|
|
49
|
+
def trim_old_messages(messages: List[ChatCompletionMessageParam]) -> List[ChatCompletionMessageParam]:
|
|
47
50
|
"""
|
|
48
51
|
Trims metadata sections from messages that are older than the specified number of recent messages.
|
|
49
52
|
We do this in order to reduce the token count of the messages, which helps us stay under the token limit for the LLM.
|
|
50
53
|
"""
|
|
51
|
-
if len(messages) <=
|
|
54
|
+
if len(messages) <= MESSAGE_HISTORY_TRIM_THRESHOLD:
|
|
52
55
|
return messages
|
|
53
56
|
|
|
54
57
|
# Process all messages except the keep_recent most recent ones.
|
|
55
58
|
# Only trim user messages, which is where this metadata lives.
|
|
56
59
|
# We want to not edit the system messages, as they contain important information / examples.
|
|
57
|
-
for i in range(len(messages) -
|
|
60
|
+
for i in range(len(messages) - MESSAGE_HISTORY_TRIM_THRESHOLD):
|
|
58
61
|
content = messages[i].get("content")
|
|
59
62
|
|
|
60
63
|
is_user_message = messages[i].get("role") == "user"
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any, Dict, Optional, Callable, Union, AsyncGenerator
|
|
8
|
+
from mito_ai.completions.models import MessageType, CompletionReply, CompletionStreamChunk, CompletionItem
|
|
9
|
+
from mito_ai.utils.server_limits import check_mito_server_quota, update_mito_server_quota
|
|
10
|
+
from tornado.httpclient import HTTPResponse
|
|
11
|
+
from mito_ai.constants import MITO_GEMINI_URL
|
|
12
|
+
from mito_ai.utils.utils import _create_http_client
|
|
13
|
+
|
|
14
|
+
MITO_ERROR_MARKER = "MITO_ERROR_MARKER:"
|
|
15
|
+
|
|
16
|
+
class ProviderCompletionException(Exception):
|
|
17
|
+
"""Custom exception for Mito server errors that converts well to CompletionError."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, error_message: str, provider_name: str = "LLM Provider", error_type: str = "LLMProviderError"):
|
|
20
|
+
self.error_message = error_message
|
|
21
|
+
self.provider_name = provider_name
|
|
22
|
+
self.error_type = error_type
|
|
23
|
+
|
|
24
|
+
# Create user-friendly title and hint
|
|
25
|
+
self.user_friendly_title = f"{provider_name} Error: {error_message}"
|
|
26
|
+
self.user_friendly_hint = f"There was a problem with {provider_name}. Try switching to a different model and trying again."
|
|
27
|
+
|
|
28
|
+
# Set args[0] for fallback compatibility
|
|
29
|
+
super().__init__(self.user_friendly_title)
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return f"{self.provider_name} Error: {self.error_message}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def get_response_from_mito_server(
|
|
36
|
+
url: str,
|
|
37
|
+
headers: dict,
|
|
38
|
+
data: Dict[str, Any],
|
|
39
|
+
timeout: int,
|
|
40
|
+
max_retries: int,
|
|
41
|
+
message_type: MessageType,
|
|
42
|
+
provider_name: str = "Mito Server"
|
|
43
|
+
) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Get a response from the Mito server.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ProviderCompletionException: When the server returns an error or invalid response
|
|
49
|
+
Exception: For network/HTTP errors (let these bubble up to be handled by retry logic)
|
|
50
|
+
"""
|
|
51
|
+
# First check the mito server quota. If the user has reached the limit, we raise an exception.
|
|
52
|
+
check_mito_server_quota(message_type)
|
|
53
|
+
|
|
54
|
+
http_client, http_client_timeout = _create_http_client(timeout, max_retries)
|
|
55
|
+
start_time = time.time()
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
res = await http_client.fetch(
|
|
59
|
+
url,
|
|
60
|
+
method="POST",
|
|
61
|
+
headers=headers,
|
|
62
|
+
body=json.dumps(data),
|
|
63
|
+
request_timeout=http_client_timeout
|
|
64
|
+
)
|
|
65
|
+
print(f"Mito server request completed in {time.time() - start_time:.2f} seconds")
|
|
66
|
+
|
|
67
|
+
# Parse and validate response
|
|
68
|
+
try:
|
|
69
|
+
content = json.loads(res.body.decode("utf-8"))
|
|
70
|
+
|
|
71
|
+
if "completion" in content:
|
|
72
|
+
return content["completion"] # type: ignore
|
|
73
|
+
elif "error" in content:
|
|
74
|
+
# Server returned an error
|
|
75
|
+
raise ProviderCompletionException(content['error'], provider_name=provider_name)
|
|
76
|
+
else:
|
|
77
|
+
# Invalid response format
|
|
78
|
+
raise ProviderCompletionException(f"No completion found in response: {content}", provider_name=provider_name)
|
|
79
|
+
except ProviderCompletionException:
|
|
80
|
+
# Re-raise ProviderCompletionException as-is
|
|
81
|
+
raise
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise ProviderCompletionException(f"Error parsing response: {str(e)}", provider_name=provider_name)
|
|
84
|
+
|
|
85
|
+
finally:
|
|
86
|
+
try:
|
|
87
|
+
# We always update the quota, even if there is an error
|
|
88
|
+
update_mito_server_quota(message_type)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
http_client.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def stream_response_from_mito_server(
|
|
96
|
+
url: str,
|
|
97
|
+
headers: Dict[str, str],
|
|
98
|
+
data: Dict[str, Any],
|
|
99
|
+
timeout: int,
|
|
100
|
+
max_retries: int,
|
|
101
|
+
message_type: MessageType,
|
|
102
|
+
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None],
|
|
103
|
+
message_id: str,
|
|
104
|
+
chunk_processor: Optional[Callable[[str], str]] = None,
|
|
105
|
+
provider_name: str = "Mito Server",
|
|
106
|
+
) -> AsyncGenerator[str, None]:
|
|
107
|
+
"""
|
|
108
|
+
Stream responses from the Mito server.
|
|
109
|
+
|
|
110
|
+
This is a unified streaming function that can be used by all providers (OpenAI, Anthropic, Gemini).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
url: The Mito server URL to stream from
|
|
114
|
+
headers: Request headers
|
|
115
|
+
data: Request data
|
|
116
|
+
timeout: Request timeout in seconds
|
|
117
|
+
max_retries: Maximum number of retries
|
|
118
|
+
message_type: The message type for quota tracking
|
|
119
|
+
provider_name: Name of the provider for error messages
|
|
120
|
+
reply_fn: Optional function to call with each chunk for streaming replies
|
|
121
|
+
message_id: The message ID to track the request
|
|
122
|
+
chunk_processor: Optional function to process chunks before yielding (e.g., for Gemini's special processing)
|
|
123
|
+
|
|
124
|
+
Yields:
|
|
125
|
+
Chunks of text from the streaming response
|
|
126
|
+
"""
|
|
127
|
+
# Check the mito server quota
|
|
128
|
+
check_mito_server_quota(message_type)
|
|
129
|
+
|
|
130
|
+
# Create HTTP client with appropriate timeout settings
|
|
131
|
+
http_client, http_client_timeout = _create_http_client(timeout, max_retries)
|
|
132
|
+
|
|
133
|
+
# Set up streaming infrastructure
|
|
134
|
+
start_time = time.time()
|
|
135
|
+
chunk_queue: asyncio.Queue[str] = asyncio.Queue()
|
|
136
|
+
fetch_complete = False
|
|
137
|
+
|
|
138
|
+
# Define a callback to process chunks and add them to the queue
|
|
139
|
+
def chunk_callback(chunk: bytes) -> None:
|
|
140
|
+
try:
|
|
141
|
+
chunk_str = chunk.decode('utf-8')
|
|
142
|
+
asyncio.create_task(chunk_queue.put(chunk_str))
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print(f"Error processing {provider_name} streaming chunk: {str(e)}")
|
|
145
|
+
|
|
146
|
+
# Execute the streaming request
|
|
147
|
+
fetch_future = None
|
|
148
|
+
try:
|
|
149
|
+
fetch_future = http_client.fetch(
|
|
150
|
+
url,
|
|
151
|
+
method="POST",
|
|
152
|
+
headers=headers,
|
|
153
|
+
body=json.dumps(data),
|
|
154
|
+
request_timeout=http_client_timeout,
|
|
155
|
+
streaming_callback=chunk_callback
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Create a task to wait for the fetch to complete
|
|
159
|
+
async def wait_for_fetch() -> None:
|
|
160
|
+
try:
|
|
161
|
+
await fetch_future
|
|
162
|
+
nonlocal fetch_complete
|
|
163
|
+
fetch_complete = True
|
|
164
|
+
print(f"{provider_name} fetch completed")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"Error in {provider_name} fetch: {str(e)}")
|
|
167
|
+
raise
|
|
168
|
+
|
|
169
|
+
# Start the task to wait for fetch completion
|
|
170
|
+
fetch_task = asyncio.create_task(wait_for_fetch())
|
|
171
|
+
|
|
172
|
+
# Yield chunks as they arrive
|
|
173
|
+
while not (fetch_complete and chunk_queue.empty()):
|
|
174
|
+
try:
|
|
175
|
+
# Wait for a chunk with a timeout to prevent deadlocks
|
|
176
|
+
chunk = await asyncio.wait_for(chunk_queue.get(), timeout=0.1)
|
|
177
|
+
|
|
178
|
+
# Process chunk if processor is provided
|
|
179
|
+
processed_chunk = chunk
|
|
180
|
+
if chunk_processor:
|
|
181
|
+
processed_chunk = chunk_processor(chunk)
|
|
182
|
+
|
|
183
|
+
# Check if this chunk contains an error marker
|
|
184
|
+
if processed_chunk.startswith(MITO_ERROR_MARKER):
|
|
185
|
+
error_message = processed_chunk[len(MITO_ERROR_MARKER):]
|
|
186
|
+
print(f"Detected error in {provider_name} stream: {error_message}")
|
|
187
|
+
raise ProviderCompletionException(error_message, provider_name=provider_name)
|
|
188
|
+
|
|
189
|
+
if reply_fn is not None and message_id is not None:
|
|
190
|
+
# Send the chunk directly to the frontend
|
|
191
|
+
reply_fn(CompletionStreamChunk(
|
|
192
|
+
parent_id=message_id,
|
|
193
|
+
chunk=CompletionItem(
|
|
194
|
+
content=processed_chunk,
|
|
195
|
+
isIncomplete=True,
|
|
196
|
+
token=message_id,
|
|
197
|
+
),
|
|
198
|
+
done=False,
|
|
199
|
+
))
|
|
200
|
+
|
|
201
|
+
yield chunk
|
|
202
|
+
except asyncio.TimeoutError:
|
|
203
|
+
# No chunk available within timeout, check if fetch is complete
|
|
204
|
+
if fetch_complete and chunk_queue.empty():
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
# Otherwise continue waiting for chunks
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
print(f"\n{provider_name} stream completed in {time.time() - start_time:.2f} seconds")
|
|
211
|
+
|
|
212
|
+
if reply_fn is not None and message_id is not None:
|
|
213
|
+
# Send a final chunk to indicate completion
|
|
214
|
+
reply_fn(CompletionStreamChunk(
|
|
215
|
+
parent_id=message_id,
|
|
216
|
+
chunk=CompletionItem(
|
|
217
|
+
content="",
|
|
218
|
+
isIncomplete=False,
|
|
219
|
+
token=message_id,
|
|
220
|
+
),
|
|
221
|
+
done=True,
|
|
222
|
+
))
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"\n{provider_name} stream failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
|
|
225
|
+
# If an exception occurred, ensure the fetch future is awaited to properly clean up
|
|
226
|
+
if fetch_future:
|
|
227
|
+
try:
|
|
228
|
+
await fetch_future
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
raise
|
|
232
|
+
finally:
|
|
233
|
+
# Clean up resources
|
|
234
|
+
try:
|
|
235
|
+
# We always update the quota, even if there is an error
|
|
236
|
+
update_mito_server_quota(message_type)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
http_client.close()
|
|
241
|
+
|
|
242
|
+
|