mito-ai 0.1.36__py3-none-any.whl → 0.1.37__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.
Potentially problematic release.
This version of mito-ai might be problematic. Click here for more details.
- mito_ai/__init__.py +6 -4
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +3 -10
- mito_ai/app_builder/handlers.py +89 -11
- mito_ai/app_builder/models.py +3 -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/completions/completion_handlers/chat_completion_handler.py +2 -2
- mito_ai/completions/models.py +7 -6
- mito_ai/completions/prompt_builders/agent_execution_prompt.py +8 -3
- mito_ai/completions/prompt_builders/agent_system_message.py +21 -7
- mito_ai/completions/prompt_builders/chat_prompt.py +18 -11
- mito_ai/completions/prompt_builders/utils.py +53 -10
- mito_ai/constants.py +11 -1
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +112 -0
- mito_ai/streamlit_conversion/streamlit_system_prompt.py +42 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +96 -0
- mito_ai/streamlit_conversion/validate_and_run_streamlit_code.py +207 -0
- mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
- mito_ai/tests/streamlit_conversion/__init__.py +3 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +265 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +197 -0
- mito_ai/tests/streamlit_conversion/test_validate_and_run_streamlit_code.py +418 -0
- mito_ai/tests/test_constants.py +18 -3
- mito_ai/utils/anthropic_utils.py +18 -70
- mito_ai/utils/gemini_utils.py +22 -73
- mito_ai/utils/mito_server_utils.py +147 -4
- mito_ai/utils/open_ai_utils.py +18 -107
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js +1165 -539
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js.map +1 -0
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5c9333902dce30642119.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js +18 -14
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js.map +1 -0
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +6 -2
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/METADATA +1 -1
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/RECORD +51 -38
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js.map +0 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5c9333902dce30642119.js.map +0 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js.map +0 -1
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.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.36.dist-info → mito_ai-0.1.37.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/licenses/LICENSE +0 -0
mito_ai/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from mito_ai.version_check import VersionCheckHandler
|
|
|
11
11
|
from mito_ai.db.urls import get_db_urls
|
|
12
12
|
from mito_ai.settings.urls import get_settings_urls
|
|
13
13
|
from mito_ai.rules.urls import get_rules_urls
|
|
14
|
+
from mito_ai.auth.urls import get_auth_urls
|
|
14
15
|
try:
|
|
15
16
|
from _version import __version__
|
|
16
17
|
except ImportError:
|
|
@@ -65,10 +66,11 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
|
|
|
65
66
|
]
|
|
66
67
|
|
|
67
68
|
# REST API endpoints
|
|
68
|
-
handlers.extend(get_db_urls(base_url))
|
|
69
|
-
handlers.extend(get_settings_urls(base_url))
|
|
70
|
-
handlers.extend(get_rules_urls(base_url))
|
|
71
|
-
handlers.extend(get_log_urls(base_url))
|
|
69
|
+
handlers.extend(get_db_urls(base_url)) # type: ignore
|
|
70
|
+
handlers.extend(get_settings_urls(base_url)) # type: ignore
|
|
71
|
+
handlers.extend(get_rules_urls(base_url)) # type: ignore
|
|
72
|
+
handlers.extend(get_log_urls(base_url)) # type: ignore
|
|
73
|
+
handlers.extend(get_auth_urls(base_url)) # type: ignore
|
|
72
74
|
|
|
73
75
|
web_app.add_handlers(host_pattern, handlers)
|
|
74
76
|
server_app.log.info("Loaded the mito_ai server extension")
|
mito_ai/_version.py
CHANGED
mito_ai/anthropic_client.py
CHANGED
|
@@ -232,18 +232,11 @@ class AnthropicClient:
|
|
|
232
232
|
system=anthropic_system_prompt,
|
|
233
233
|
messages=anthropic_messages,
|
|
234
234
|
stream=True,
|
|
235
|
-
message_type=message_type
|
|
235
|
+
message_type=message_type,
|
|
236
|
+
reply_fn=reply_fn,
|
|
237
|
+
message_id=message_id
|
|
236
238
|
):
|
|
237
239
|
accumulated_response += stram_chunk
|
|
238
|
-
reply_fn(CompletionStreamChunk(
|
|
239
|
-
parent_id=message_id,
|
|
240
|
-
chunk=CompletionItem(
|
|
241
|
-
content=stram_chunk,
|
|
242
|
-
isIncomplete=True,
|
|
243
|
-
token=message_id,
|
|
244
|
-
),
|
|
245
|
-
done=False,
|
|
246
|
-
))
|
|
247
240
|
|
|
248
241
|
return accumulated_response
|
|
249
242
|
|
mito_ai/app_builder/handlers.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import Any, Union
|
|
|
8
8
|
import zipfile
|
|
9
9
|
import tempfile
|
|
10
10
|
from mito_ai.utils.create import initialize_user
|
|
11
|
+
from mito_ai.utils.version_utils import is_pro
|
|
11
12
|
from mito_ai.utils.websocket_base import BaseWebSocketHandler
|
|
12
13
|
from mito_ai.app_builder.models import (
|
|
13
14
|
BuildAppReply,
|
|
@@ -15,10 +16,12 @@ from mito_ai.app_builder.models import (
|
|
|
15
16
|
ErrorMessage,
|
|
16
17
|
MessageType
|
|
17
18
|
)
|
|
19
|
+
from mito_ai.streamlit_conversion.streamlit_agent_handler import streamlit_handler
|
|
18
20
|
from mito_ai.logger import get_logger
|
|
19
21
|
from mito_ai.constants import ACTIVE_STREAMLIT_BASE_URL
|
|
20
22
|
import requests
|
|
21
23
|
|
|
24
|
+
|
|
22
25
|
class AppBuilderHandler(BaseWebSocketHandler):
|
|
23
26
|
"""Handler for app building requests."""
|
|
24
27
|
|
|
@@ -62,6 +65,10 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
62
65
|
self.log.debug("App builder message received: %s", message)
|
|
63
66
|
|
|
64
67
|
try:
|
|
68
|
+
# Ensure message is a string before parsing
|
|
69
|
+
if not isinstance(message, str):
|
|
70
|
+
raise ValueError("Message must be a string")
|
|
71
|
+
|
|
65
72
|
parsed_message = self.parse_message(message)
|
|
66
73
|
message_type = parsed_message.get('type')
|
|
67
74
|
|
|
@@ -98,8 +105,10 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
98
105
|
message: The parsed message.
|
|
99
106
|
"""
|
|
100
107
|
message_id = message.get('message_id', '') # Default to empty string if not present
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
notebook_path = message.get('notebook_path')
|
|
109
|
+
app_path = message.get('app_path')
|
|
110
|
+
jwt_token = message.get('jwt_token', '') # Extract JWT token from request, default to empty string
|
|
111
|
+
|
|
103
112
|
if not message_id:
|
|
104
113
|
self.log.error("Missing message_id in request")
|
|
105
114
|
return
|
|
@@ -115,13 +124,37 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
115
124
|
error=error
|
|
116
125
|
))
|
|
117
126
|
return
|
|
118
|
-
|
|
127
|
+
|
|
128
|
+
# Validate JWT token if provided
|
|
129
|
+
if jwt_token and jwt_token != 'placeholder-jwt-token':
|
|
130
|
+
self.log.info(f"Validating JWT token: {jwt_token[:20]}...")
|
|
131
|
+
is_valid = self._validate_jwt_token(jwt_token)
|
|
132
|
+
if not is_valid:
|
|
133
|
+
self.log.error("JWT token validation failed")
|
|
134
|
+
error = AppBuilderError(
|
|
135
|
+
error_type="Unauthorized",
|
|
136
|
+
title="Invalid authentication token",
|
|
137
|
+
hint="Please sign in again to deploy your app."
|
|
138
|
+
)
|
|
139
|
+
self.reply(BuildAppReply(
|
|
140
|
+
parent_id=message_id,
|
|
141
|
+
url="",
|
|
142
|
+
error=error
|
|
143
|
+
))
|
|
144
|
+
return
|
|
145
|
+
else:
|
|
146
|
+
self.log.info("JWT token validation successful")
|
|
147
|
+
else:
|
|
148
|
+
self.log.warning("No JWT token provided or using placeholder token")
|
|
149
|
+
|
|
119
150
|
try:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
|
|
152
|
+
success_flag, result_message = await streamlit_handler(str(notebook_path) if notebook_path else "", app_path)
|
|
153
|
+
if not success_flag:
|
|
154
|
+
raise Exception(result_message)
|
|
155
|
+
|
|
156
|
+
deploy_url = await self._deploy_app(app_path, jwt_token)
|
|
157
|
+
|
|
125
158
|
# Send the response
|
|
126
159
|
self.reply(BuildAppReply(
|
|
127
160
|
parent_id=message_id,
|
|
@@ -137,12 +170,47 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
137
170
|
error=error
|
|
138
171
|
))
|
|
139
172
|
|
|
140
|
-
|
|
173
|
+
def _validate_jwt_token(self, token: str) -> bool:
|
|
174
|
+
"""Basic JWT token validation logic.
|
|
175
|
+
|
|
176
|
+
In a production environment, you would:
|
|
177
|
+
1. Decode the JWT token
|
|
178
|
+
2. Verify the signature using AWS Cognito public keys
|
|
179
|
+
3. Check the expiration time
|
|
180
|
+
4. Validate the issuer and audience claims
|
|
181
|
+
|
|
182
|
+
For now, we'll do a basic check that the token exists and has a reasonable format.
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
# Basic JWT format validation (header.payload.signature)
|
|
186
|
+
if not token or '.' not in token:
|
|
187
|
+
self.log.error("Token is empty or missing dots")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
parts = token.split('.')
|
|
191
|
+
if len(parts) != 3:
|
|
192
|
+
self.log.error("Token does not have 3 parts")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
# Check for placeholder token
|
|
196
|
+
if token == 'placeholder-jwt-token':
|
|
197
|
+
self.log.error("Placeholder token detected")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self.log.error(f"Error validating JWT token: {e}")
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _deploy_app(self, app_path: str, jwt_token: str = '') -> str:
|
|
141
208
|
"""Deploy the app using pre-signed URLs.
|
|
142
209
|
|
|
143
210
|
Args:
|
|
144
211
|
app_path: Path to the app file.
|
|
145
|
-
|
|
212
|
+
jwt_token: JWT token for authentication (optional)
|
|
213
|
+
|
|
146
214
|
Returns:
|
|
147
215
|
The URL of the deployed app.
|
|
148
216
|
"""
|
|
@@ -153,7 +221,17 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
153
221
|
try:
|
|
154
222
|
# Step 1: Get pre-signed URL from API
|
|
155
223
|
self.log.info("Getting pre-signed upload URL...")
|
|
156
|
-
|
|
224
|
+
|
|
225
|
+
# Prepare headers with JWT token if provided
|
|
226
|
+
headers = {}
|
|
227
|
+
if jwt_token and jwt_token != 'placeholder-jwt-token':
|
|
228
|
+
headers['Authorization'] = f'Bearer {jwt_token}'
|
|
229
|
+
else:
|
|
230
|
+
self.log.warning("No JWT token provided for API request")
|
|
231
|
+
|
|
232
|
+
headers["Subscription-Tier"] = 'Pro' if is_pro() else 'Standard'
|
|
233
|
+
|
|
234
|
+
url_response = requests.get(f"{ACTIVE_STREAMLIT_BASE_URL}/get-upload-url?app_name={app_name}", headers=headers)
|
|
157
235
|
url_response.raise_for_status()
|
|
158
236
|
|
|
159
237
|
url_data = url_response.json()
|
mito_ai/app_builder/models.py
CHANGED
mito_ai/auth/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Authorization
|
|
2
|
+
|
|
3
|
+
### Authorization Code Flow:
|
|
4
|
+
|
|
5
|
+
1. User clicks "Sign In" → then they are redirected to Cognito's hosted UI
|
|
6
|
+
2. User authenticates → then Cognito redirects back with an authorization code
|
|
7
|
+
3. Our backend then exchanges the code for JWT tokens
|
|
8
|
+
|
|
9
|
+
The authorization code in step2 is a short-lived, one-time use code.
|
|
10
|
+
To exchange the authorization code for tokens, we have to make a POST request to Cognito's token endpoint:
|
|
11
|
+
POST https://your-domain.auth.region.amazoncognito.com/oauth2/token
|
|
12
|
+
|
|
13
|
+
The JWT tokens received will provide an hour's session for the user for the next deployments, without having to re-login.
|
|
14
|
+
|
|
15
|
+
### The response from this request contains 3 JWT tokens:
|
|
16
|
+
1. Access Token - Used to call APIs (expires in 1 hour by default)
|
|
17
|
+
2. ID Token - Contains user identity information (name, email, etc.)
|
|
18
|
+
3. Refresh Token - Used to get new access/ID tokens when they expire
|
mito_ai/auth/__init__.py
ADDED
mito_ai/auth/handlers.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import requests
|
|
7
|
+
import tornado
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from jupyter_server.base.handlers import APIHandler
|
|
10
|
+
from mito_ai.logger import get_logger
|
|
11
|
+
from mito_ai.constants import ACTIVE_COGNITO_CONFIG
|
|
12
|
+
from typing import Dict, Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthHandler(APIHandler):
|
|
16
|
+
"""Handler for authentication operations."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def log(self) -> logging.Logger:
|
|
20
|
+
"""Use Mito AI logger."""
|
|
21
|
+
return get_logger()
|
|
22
|
+
|
|
23
|
+
@tornado.web.authenticated
|
|
24
|
+
def post(self) -> None:
|
|
25
|
+
"""Exchange authorization code for JWT tokens."""
|
|
26
|
+
try:
|
|
27
|
+
data = json.loads(self.request.body)
|
|
28
|
+
code = data.get('code')
|
|
29
|
+
|
|
30
|
+
if not code:
|
|
31
|
+
self.set_status(400)
|
|
32
|
+
self.finish(json.dumps({"error": "Authorization code is required"}))
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Exchange authorization code for tokens
|
|
36
|
+
token_response = self._exchange_code_for_tokens(code)
|
|
37
|
+
|
|
38
|
+
if token_response.get('error'):
|
|
39
|
+
self.set_status(400)
|
|
40
|
+
self.finish(json.dumps({"error": token_response['error']}))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Return the tokens to the client
|
|
44
|
+
self.finish(json.dumps({
|
|
45
|
+
"access_token": token_response.get('access_token'),
|
|
46
|
+
"id_token": token_response.get('id_token'),
|
|
47
|
+
"refresh_token": token_response.get('refresh_token'),
|
|
48
|
+
"expires_in": token_response.get('expires_in')
|
|
49
|
+
}))
|
|
50
|
+
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
self.set_status(400)
|
|
53
|
+
self.finish(json.dumps({"error": "Invalid JSON in request body"}))
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.log.error(f"Error in auth handler: {e}")
|
|
56
|
+
self.set_status(500)
|
|
57
|
+
self.finish(json.dumps({"error": "Internal server error"}))
|
|
58
|
+
|
|
59
|
+
def _exchange_code_for_tokens(self, code: str) -> Dict[str, Any]:
|
|
60
|
+
"""Exchange authorization code for JWT tokens using AWS Cognito."""
|
|
61
|
+
try:
|
|
62
|
+
# Prepare the token request
|
|
63
|
+
token_data = {
|
|
64
|
+
'grant_type': 'authorization_code',
|
|
65
|
+
'client_id': ACTIVE_COGNITO_CONFIG['CLIENT_ID'],
|
|
66
|
+
'code': code,
|
|
67
|
+
'redirect_uri': ACTIVE_COGNITO_CONFIG['REDIRECT_URI']
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Add client secret if configured
|
|
71
|
+
if ACTIVE_COGNITO_CONFIG['CLIENT_SECRET']:
|
|
72
|
+
token_data['client_secret'] = ACTIVE_COGNITO_CONFIG['CLIENT_SECRET']
|
|
73
|
+
|
|
74
|
+
# Make the token request
|
|
75
|
+
response = requests.post(
|
|
76
|
+
ACTIVE_COGNITO_CONFIG['TOKEN_ENDPOINT'],
|
|
77
|
+
data=token_data,
|
|
78
|
+
headers={'Content-Type': 'application/x-www-form-urlencoded'}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if response.status_code == 200:
|
|
82
|
+
token_response: Dict[str, Any] = response.json()
|
|
83
|
+
|
|
84
|
+
current_time = datetime.now(timezone.utc)
|
|
85
|
+
self.log.info(f"Token exchange successful at {current_time.isoformat()}")
|
|
86
|
+
return token_response
|
|
87
|
+
else:
|
|
88
|
+
self.log.error(f"Token exchange failed: {response.status_code} - {response.text}")
|
|
89
|
+
return {"error": "Failed to exchange authorization code for tokens"}
|
|
90
|
+
|
|
91
|
+
except requests.exceptions.RequestException as e:
|
|
92
|
+
self.log.error(f"Request error during token exchange: {e}")
|
|
93
|
+
return {"error": "Network error during token exchange"}
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.log.error(f"Unexpected error during token exchange: {e}")
|
|
96
|
+
return {"error": "Unexpected error during token exchange"}
|
mito_ai/auth/urls.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
5
|
+
from jupyter_server.utils import url_path_join
|
|
6
|
+
from .handlers import AuthHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_auth_urls(base_url: str) -> List[Tuple[str, type]]:
|
|
10
|
+
"""Get the auth URL patterns."""
|
|
11
|
+
return [
|
|
12
|
+
(url_path_join(base_url, "mito-ai", "auth", "token"), AuthHandler),
|
|
13
|
+
]
|
|
@@ -42,7 +42,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
|
|
|
42
42
|
metadata.activeCellId,
|
|
43
43
|
metadata.base64EncodedActiveCellOutput is not None and metadata.base64EncodedActiveCellOutput != '',
|
|
44
44
|
metadata.input,
|
|
45
|
-
metadata.
|
|
45
|
+
metadata.additionalContext
|
|
46
46
|
)
|
|
47
47
|
display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
|
|
48
48
|
|
|
@@ -105,7 +105,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
|
|
|
105
105
|
metadata.activeCellId,
|
|
106
106
|
metadata.base64EncodedActiveCellOutput is not None and metadata.base64EncodedActiveCellOutput != '',
|
|
107
107
|
metadata.input,
|
|
108
|
-
metadata.
|
|
108
|
+
metadata.additionalContext
|
|
109
109
|
)
|
|
110
110
|
display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
|
|
111
111
|
|
mito_ai/completions/models.py
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import traceback
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import List, Literal, Optional, NewType, Dict
|
|
7
7
|
from openai.types.chat import ChatCompletionMessageParam
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
-
|
|
9
|
+
from pydantic import BaseModel
|
|
11
10
|
|
|
12
11
|
# The ThreadID is the unique identifier for the chat thread.
|
|
13
12
|
ThreadID = NewType('ThreadID', str)
|
|
@@ -35,6 +34,7 @@ class AgentResponse(BaseModel):
|
|
|
35
34
|
cell_update: Optional[CellUpdate]
|
|
36
35
|
get_cell_output_cell_id: Optional[str]
|
|
37
36
|
next_steps: Optional[List[str]]
|
|
37
|
+
analysis_assumptions: Optional[List[str]]
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
@dataclass(frozen=True)
|
|
@@ -63,6 +63,7 @@ class MessageType(Enum):
|
|
|
63
63
|
GET_THREADS = "get_threads"
|
|
64
64
|
DELETE_THREAD = "delete_thread"
|
|
65
65
|
UPDATE_MODEL_CONFIG = "update_model_config"
|
|
66
|
+
STREAMLIT_CONVERSION = "streamlit_conversion"
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
@dataclass(frozen=True)
|
|
@@ -84,7 +85,7 @@ class ChatMessageMetadata():
|
|
|
84
85
|
base64EncodedActiveCellOutput: Optional[str] = None
|
|
85
86
|
index: Optional[int] = None
|
|
86
87
|
stream: bool = False
|
|
87
|
-
|
|
88
|
+
additionalContext: Optional[List[Dict[str, str]]] = None
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
@dataclass(frozen=True)
|
|
@@ -98,7 +99,7 @@ class AgentExecutionMetadata():
|
|
|
98
99
|
variables: Optional[List[str]] = None
|
|
99
100
|
files: Optional[List[str]] = None
|
|
100
101
|
index: Optional[int] = None
|
|
101
|
-
|
|
102
|
+
additionalContext: Optional[List[Dict[str, str]]] = None
|
|
102
103
|
|
|
103
104
|
@dataclass(frozen=True)
|
|
104
105
|
class AgentSmartDebugMetadata():
|
|
@@ -135,7 +136,7 @@ class InlineCompleterMetadata():
|
|
|
135
136
|
suffix: str
|
|
136
137
|
variables: Optional[List[str]] = None
|
|
137
138
|
files: Optional[List[str]] = None
|
|
138
|
-
|
|
139
|
+
|
|
139
140
|
@dataclass(frozen=True)
|
|
140
141
|
class CompletionRequest:
|
|
141
142
|
"""
|
|
@@ -8,14 +8,17 @@ from mito_ai.completions.prompt_builders.prompt_constants import (
|
|
|
8
8
|
VARIABLES_SECTION_HEADING,
|
|
9
9
|
cell_update_output_str
|
|
10
10
|
)
|
|
11
|
-
from mito_ai.completions.prompt_builders.utils import
|
|
11
|
+
from mito_ai.completions.prompt_builders.utils import (
|
|
12
|
+
get_rules_str,
|
|
13
|
+
get_selected_context_str,
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
def create_agent_execution_prompt(md: AgentExecutionMetadata) -> str:
|
|
14
17
|
variables_str = '\n'.join([f"{variable}" for variable in md.variables or []])
|
|
15
18
|
files_str = '\n'.join([f"{file}" for file in md.files or []])
|
|
16
19
|
ai_optimized_cells_str = '\n'.join([f"{cell}" for cell in md.aiOptimizedCells or []])
|
|
17
|
-
rules_str = get_rules_str(md.
|
|
18
|
-
|
|
20
|
+
rules_str = get_rules_str(md.additionalContext)
|
|
21
|
+
selected_context_str = get_selected_context_str(md.additionalContext)
|
|
19
22
|
context_str = f"""Remember to choose the correct tool to respond with.
|
|
20
23
|
|
|
21
24
|
{rules_str}
|
|
@@ -30,6 +33,8 @@ def create_agent_execution_prompt(md: AgentExecutionMetadata) -> str:
|
|
|
30
33
|
{FILES_SECTION_HEADING}
|
|
31
34
|
{files_str}
|
|
32
35
|
|
|
36
|
+
{selected_context_str}
|
|
37
|
+
|
|
33
38
|
{cell_update_output_str(md.base64EncodedActiveCellOutput is not None)}"""
|
|
34
39
|
|
|
35
40
|
task_str = '' if md.input == '' else f"""Your task:
|
|
@@ -53,7 +53,8 @@ Format:
|
|
|
53
53
|
cell_type: 'code' | 'markdown'
|
|
54
54
|
}}
|
|
55
55
|
get_cell_output_cell_id: None,
|
|
56
|
-
next_steps: None
|
|
56
|
+
next_steps: None,
|
|
57
|
+
analysis_assumptions: None
|
|
57
58
|
}}
|
|
58
59
|
|
|
59
60
|
Important information:
|
|
@@ -62,6 +63,9 @@ Important information:
|
|
|
62
63
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
63
64
|
4. The code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
64
65
|
5. Important: Only use the CELL_UPDATE tool if you want to add/modify a notebook cell in response to the user's request. If the user is just sending you a friendly greeting or asking you a question about yourself, you SHOULD NOT USE A CELL_UPDATE tool because it does not require modifying the notebook. Instead, just use the FINISHED_TASK response.
|
|
66
|
+
6. The assumptions is an optional list of critical assumptions that you made about the data or analysis approach. The assumptions you list here will be displayed to the user so that they can confirm or correct the assumptions. For example: ["NaN values in the impressions column represent 0 impressions", "Only crashes with pedestrian or cyclist fatalities are considered fatal crashes", "Intervention priority combines both volume and severity to identify maximum impact opportunities"].
|
|
67
|
+
7. Only include important data and analytical assumptions that if incorrect would fundamentally change your analysis conclusions. These should be data handling decisions, methodological choices, and definitional boundaries. Do not include: obvious statements ("Each record is counted once"), result interpretation guidance ("Gaps in the plot represent zero values"), display choices ("Data is sorted for clarity"), internal reasoning ("Bar chart is better than line plot"), or environment assumptions ("Library X is installed"). Prioritize quality over quantity - include only the most critical assumptions or None if there are no critical assumptions made in this step that have not already be shared with the user. If you ever doubt whether an assumption is critical enough to be shared with the user as an assumption, don't include it. Most messages should not include an assumption.
|
|
68
|
+
8. Do not include the same assumption or variations of the same assumption multiple times in the same conversation. Once you have presented the assumption to the user, they will already have the opportunity to confirm or correct it so do not include it again.
|
|
65
69
|
|
|
66
70
|
#### Cell Addition:
|
|
67
71
|
When you want to add a new cell to the notebook, respond in this format
|
|
@@ -78,7 +82,8 @@ Format:
|
|
|
78
82
|
cell_type: 'code' | 'markdown'
|
|
79
83
|
}}
|
|
80
84
|
get_cell_output_cell_id: None,
|
|
81
|
-
next_steps: None
|
|
85
|
+
next_steps: None,
|
|
86
|
+
analysis_assumptions: None
|
|
82
87
|
}}
|
|
83
88
|
|
|
84
89
|
Important information:
|
|
@@ -87,6 +92,9 @@ Important information:
|
|
|
87
92
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
88
93
|
4. code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
89
94
|
5. The cell_type should only be 'markdown' if there is no code to add. There may be times where the code has comments. These are still code cells and should have the cell_type 'code'. Any cells that are labeled 'markdown' will be converted to markdown cells by the user.
|
|
95
|
+
6. The assumptions is an optional list of critical assumptions that you made about the data or analysis approach. The assumptions you list here will be displayed to the user so that they can confirm or correct the assumptions. For example: ["NaN values in the impressions column represent 0 impressions", "Only crashes with pedestrian or cyclist fatalities are considered fatal crashes", "Intervention priority combines both volume and severity to identify maximum impact opportunities"].
|
|
96
|
+
7. Only include important data and analytical assumptions that if incorrect would fundamentally change your analysis conclusions. These should be data handling decisions, methodological choices, and definitional boundaries. Do not include: obvious statements ("Each record is counted once"), result interpretation guidance ("Gaps in the plot represent zero values"), display choices ("Data is sorted for clarity"), internal reasoning ("Bar chart is better than line plot"), or environment assumptions ("Library X is installed"). Prioritize quality over quantity - include only the most critical assumptions or None if there are no critical assumptions made in this step that have not already be shared with the user. If you ever doubt whether an assumption is critical enough to be shared with the user as an assumption, don't include it. Most messages should not include an assumption.
|
|
97
|
+
8. Do not include the same assumption or variations of the same assumption multiple times in the same conversation. Once you have presented the assumption to the user, they will already have the opportunity to confirm or correct it so do not include it again.
|
|
90
98
|
|
|
91
99
|
<Cell Modification Example>
|
|
92
100
|
Jupyter Notebook:
|
|
@@ -134,7 +142,8 @@ Output:
|
|
|
134
142
|
cell_type: 'code'
|
|
135
143
|
}},
|
|
136
144
|
get_cell_output_cell_id: None,
|
|
137
|
-
next_steps: None
|
|
145
|
+
next_steps: None,
|
|
146
|
+
analysis_assumptions: None
|
|
138
147
|
}}
|
|
139
148
|
|
|
140
149
|
</Cell Modification Example>
|
|
@@ -184,7 +193,8 @@ Output:
|
|
|
184
193
|
code_summary: "Plotting total_price"
|
|
185
194
|
}},
|
|
186
195
|
get_cell_output_cell_id: None,
|
|
187
|
-
next_steps: None
|
|
196
|
+
next_steps: None,
|
|
197
|
+
analysis_assumptions: None
|
|
188
198
|
}}
|
|
189
199
|
|
|
190
200
|
</Cell Addition Example>
|
|
@@ -200,7 +210,8 @@ When you want to get a base64 encoded version of a cell's output, respond with t
|
|
|
200
210
|
message: str,
|
|
201
211
|
get_cell_output_cell_id: str,
|
|
202
212
|
cell_update: None,
|
|
203
|
-
next_steps: Optional[List[str]]
|
|
213
|
+
next_steps: Optional[List[str]],
|
|
214
|
+
analysis_assumptions: Optional[List[str]]
|
|
204
215
|
}}
|
|
205
216
|
|
|
206
217
|
Important information:
|
|
@@ -219,7 +230,8 @@ When you have completed the user's task, respond with a message in this format:
|
|
|
219
230
|
message: str,
|
|
220
231
|
get_cell_output_cell_id: None,
|
|
221
232
|
cell_update: None,
|
|
222
|
-
next_steps: Optional[List[str]]
|
|
233
|
+
next_steps: Optional[List[str]],
|
|
234
|
+
analysis_assumptions: None
|
|
223
235
|
}}
|
|
224
236
|
|
|
225
237
|
Important information:
|
|
@@ -230,6 +242,7 @@ Important information:
|
|
|
230
242
|
5. If you are not sure what the user might want to do next, err on the side of suggesting next steps instead of making an assumption and using more CELL_UPDATES.
|
|
231
243
|
6. If the user's task doesn't involve creating or modifying a code cell, you should respond with a FINISHED_TASK response.
|
|
232
244
|
7. If the user is just sending a friendly greeting (like "Hello", "Hi", "Hey", "How are you?", "What can you help me with?", etc.), you must respond with a FINISHED_TASK response message with a friendly message like this: "Hello! I'm Mito AI, your AI assistant for data analysis and Python programming in Jupyter notebooks. I can help you analyze datasets, create visualizations, clean data, and much more. What would you like to work on today?"
|
|
245
|
+
8. Do not include any analysis_assumptions in the FINISHED_TASK response.
|
|
233
246
|
|
|
234
247
|
<Finished Task Example 1>
|
|
235
248
|
|
|
@@ -253,7 +266,8 @@ Output:
|
|
|
253
266
|
message: "Hey there! I'm Mito AI. How can I help you today?",
|
|
254
267
|
get_cell_output_cell_id: None,
|
|
255
268
|
cell_update: None,
|
|
256
|
-
next_steps: None
|
|
269
|
+
next_steps: None,
|
|
270
|
+
analysis_assumptions: None
|
|
257
271
|
}}
|
|
258
272
|
|
|
259
273
|
</Finished Task Example 2>
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
# Copyright (c) Saga Inc.
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
|
-
from typing import List, Optional
|
|
4
|
+
from typing import List, Optional, Dict
|
|
5
5
|
from mito_ai.completions.prompt_builders.prompt_constants import (
|
|
6
6
|
ACTIVE_CELL_ID_SECTION_HEADING,
|
|
7
7
|
CHAT_CODE_FORMATTING_RULES,
|
|
8
8
|
FILES_SECTION_HEADING,
|
|
9
9
|
VARIABLES_SECTION_HEADING,
|
|
10
10
|
CODE_SECTION_HEADING,
|
|
11
|
-
get_active_cell_output_str
|
|
11
|
+
get_active_cell_output_str,
|
|
12
12
|
)
|
|
13
|
-
from mito_ai.completions.prompt_builders.utils import
|
|
13
|
+
from mito_ai.completions.prompt_builders.utils import (
|
|
14
|
+
get_rules_str,
|
|
15
|
+
get_selected_context_str,
|
|
16
|
+
)
|
|
17
|
+
|
|
14
18
|
|
|
15
19
|
def create_chat_prompt(
|
|
16
20
|
variables: List[str],
|
|
17
21
|
files: List[str],
|
|
18
|
-
active_cell_code: str,
|
|
22
|
+
active_cell_code: str,
|
|
19
23
|
active_cell_id: str,
|
|
20
24
|
has_active_cell_output: bool,
|
|
21
25
|
input: str,
|
|
22
|
-
|
|
26
|
+
additional_context: Optional[List[Dict[str, str]]] = None,
|
|
23
27
|
) -> str:
|
|
24
|
-
variables_str =
|
|
25
|
-
files_str =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
variables_str = "\n".join([f"{variable}" for variable in variables])
|
|
29
|
+
files_str = "\n".join([f"{file}" for file in files])
|
|
30
|
+
selected_context_str = get_selected_context_str(additional_context)
|
|
31
|
+
rules_str = get_rules_str(additional_context)
|
|
32
|
+
|
|
28
33
|
prompt = f"""{rules_str}
|
|
29
34
|
|
|
30
35
|
Help me complete the following task. I will provide you with a set of variables, existing code, and a task to complete.
|
|
@@ -101,9 +106,11 @@ Hey there! I'm Mito AI. How can I help you today?
|
|
|
101
106
|
{active_cell_code}
|
|
102
107
|
```
|
|
103
108
|
|
|
109
|
+
{selected_context_str}
|
|
110
|
+
|
|
104
111
|
{get_active_cell_output_str(has_active_cell_output)}
|
|
105
112
|
|
|
106
113
|
Your task: {input}
|
|
107
114
|
"""
|
|
108
|
-
|
|
109
|
-
return prompt
|
|
115
|
+
|
|
116
|
+
return prompt
|