mito-ai 0.1.41__py3-none-any.whl → 0.1.42__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 +7 -0
- mito_ai/_version.py +1 -1
- mito_ai/app_manager/__init__.py +4 -0
- mito_ai/app_manager/handlers.py +129 -0
- mito_ai/app_manager/models.py +58 -0
- mito_ai/completions/completion_handlers/agent_execution_handler.py +1 -1
- mito_ai/completions/completion_handlers/chat_completion_handler.py +2 -2
- mito_ai/completions/completion_handlers/utils.py +77 -37
- mito_ai/completions/models.py +2 -0
- mito_ai/completions/prompt_builders/utils.py +7 -0
- mito_ai/tests/completions/completion_handlers_utils_test.py +51 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a9a35b6fcc54a7bcb32c.js +807 -44
- mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a9a35b6fcc54a7bcb32c.js.map +1 -0
- mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
- mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.c7d9d8635826165de52e.js +23 -23
- mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.c7d9d8635826165de52e.js.map +1 -0
- mito_ai-0.1.42.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.42.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.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +2977 -610
- mito_ai-0.1.42.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.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +1 -481
- mito_ai-0.1.42.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.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js → mito_ai-0.1.42.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 +2 -60
- mito_ai-0.1.42.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.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
- mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/METADATA +1 -1
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/RECORD +44 -40
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js +0 -63
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js +0 -2345
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js.map +0 -1
- mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.42.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.41.dist-info → mito_ai-0.1.42.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/licenses/LICENSE +0 -0
mito_ai/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@ from mito_ai.settings.urls import get_settings_urls
|
|
|
14
14
|
from mito_ai.rules.urls import get_rules_urls
|
|
15
15
|
from mito_ai.auth.urls import get_auth_urls
|
|
16
16
|
from mito_ai.streamlit_preview.urls import get_streamlit_preview_urls
|
|
17
|
+
from mito_ai.app_manager.handlers import AppManagerHandler
|
|
17
18
|
from mito_ai.file_uploads.urls import get_file_uploads_urls
|
|
18
19
|
|
|
19
20
|
# Force Matplotlib to use the Jupyter inline backend.
|
|
@@ -24,6 +25,7 @@ from mito_ai.file_uploads.urls import get_file_uploads_urls
|
|
|
24
25
|
# We preempt this by selecting the canonical Jupyter inline backend BEFORE any
|
|
25
26
|
# Matplotlib import, so figures render inline reliably. This must run very early.
|
|
26
27
|
# See: https://github.com/streamlit/streamlit/issues/9640
|
|
28
|
+
|
|
27
29
|
import os
|
|
28
30
|
os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline"
|
|
29
31
|
|
|
@@ -82,6 +84,11 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
|
|
|
82
84
|
url_path_join(base_url, "mito-ai", "version-check"),
|
|
83
85
|
VersionCheckHandler,
|
|
84
86
|
{},
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
url_path_join(base_url, "mito-ai", "app-manager"),
|
|
90
|
+
AppManagerHandler,
|
|
91
|
+
{}
|
|
85
92
|
)
|
|
86
93
|
]
|
|
87
94
|
|
mito_ai/_version.py
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
# app_manager/handlers.py
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Union
|
|
9
|
+
from mito_ai.utils.websocket_base import BaseWebSocketHandler
|
|
10
|
+
from mito_ai.app_manager.models import (
|
|
11
|
+
App,
|
|
12
|
+
AppManagerError,
|
|
13
|
+
ManageAppRequest,
|
|
14
|
+
ManageAppReply,
|
|
15
|
+
ErrorMessage,
|
|
16
|
+
MessageType
|
|
17
|
+
)
|
|
18
|
+
from mito_ai.constants import ACTIVE_STREAMLIT_BASE_URL
|
|
19
|
+
from mito_ai.logger import get_logger
|
|
20
|
+
import requests
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AppManagerHandler(BaseWebSocketHandler):
|
|
24
|
+
"""Handler for app management requests."""
|
|
25
|
+
|
|
26
|
+
def initialize(self) -> None:
|
|
27
|
+
"""Initialize the WebSocket handler."""
|
|
28
|
+
super().initialize()
|
|
29
|
+
self.log.debug("Initializing app manager websocket connection %s", self.request.path)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def log(self) -> logging.Logger:
|
|
33
|
+
"""Use Mito AI logger."""
|
|
34
|
+
return get_logger()
|
|
35
|
+
|
|
36
|
+
async def on_message(self, message: Union[str, bytes]) -> None:
|
|
37
|
+
"""Handle incoming messages on the WebSocket."""
|
|
38
|
+
start = time.time()
|
|
39
|
+
|
|
40
|
+
# Convert bytes to string if needed
|
|
41
|
+
if isinstance(message, bytes):
|
|
42
|
+
message = message.decode('utf-8')
|
|
43
|
+
|
|
44
|
+
self.log.debug("App manager message received: %s", message)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Ensure message is a string before parsing
|
|
48
|
+
if not isinstance(message, str):
|
|
49
|
+
raise ValueError("Message must be a string")
|
|
50
|
+
|
|
51
|
+
parsed_message = self.parse_message(message)
|
|
52
|
+
message_type = parsed_message.get('type')
|
|
53
|
+
message_id = parsed_message.get('message_id')
|
|
54
|
+
|
|
55
|
+
if message_type == MessageType.MANAGE_APP.value:
|
|
56
|
+
# Handle manage app request
|
|
57
|
+
manage_app_request = ManageAppRequest(**parsed_message)
|
|
58
|
+
await self._handle_manage_app(manage_app_request)
|
|
59
|
+
else:
|
|
60
|
+
self.log.error(f"Unknown message type: {message_type}")
|
|
61
|
+
error_response = ErrorMessage(
|
|
62
|
+
error_type="InvalidRequest",
|
|
63
|
+
title=f"Unknown message type: {message_type}",
|
|
64
|
+
message_id=message_id
|
|
65
|
+
)
|
|
66
|
+
self.reply(error_response)
|
|
67
|
+
|
|
68
|
+
except ValueError as e:
|
|
69
|
+
self.log.error("Invalid app manager request", exc_info=e)
|
|
70
|
+
error_response = ErrorMessage(
|
|
71
|
+
error_type=type(e).__name__,
|
|
72
|
+
title=str(e),
|
|
73
|
+
message_id=parsed_message.get('message_id') if 'parsed_message' in locals() else None
|
|
74
|
+
)
|
|
75
|
+
self.reply(error_response)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.log.error("Error handling app manager message", exc_info=e)
|
|
78
|
+
error_response = ErrorMessage(
|
|
79
|
+
error_type=type(e).__name__,
|
|
80
|
+
title=str(e),
|
|
81
|
+
message_id=parsed_message.get('message_id') if 'parsed_message' in locals() else None
|
|
82
|
+
)
|
|
83
|
+
self.reply(error_response)
|
|
84
|
+
|
|
85
|
+
latency_ms = round((time.time() - start) * 1000)
|
|
86
|
+
self.log.info(f"App manager handler processed in {latency_ms} ms.")
|
|
87
|
+
|
|
88
|
+
async def _handle_manage_app(self, request: ManageAppRequest) -> None:
|
|
89
|
+
"""Handle a manage app request with hardcoded data."""
|
|
90
|
+
try:
|
|
91
|
+
jwt_token = request.jwt_token
|
|
92
|
+
headers = {}
|
|
93
|
+
if jwt_token and jwt_token != 'placeholder-jwt-token':
|
|
94
|
+
headers['Authorization'] = f'Bearer {jwt_token}'
|
|
95
|
+
else:
|
|
96
|
+
self.log.warning("No JWT token provided for API request")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
manage_apps_response = requests.get(f"{ACTIVE_STREAMLIT_BASE_URL}/manage-apps",
|
|
100
|
+
headers=headers)
|
|
101
|
+
manage_apps_response.raise_for_status()
|
|
102
|
+
|
|
103
|
+
apps_data = manage_apps_response.json()
|
|
104
|
+
|
|
105
|
+
# Create successful response
|
|
106
|
+
reply = ManageAppReply(
|
|
107
|
+
apps=apps_data,
|
|
108
|
+
message_id=request.message_id
|
|
109
|
+
)
|
|
110
|
+
self.reply(reply)
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.log.error(f"Error handling manage app request: {e}", exc_info=e)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
error = AppManagerError.from_exception(e)
|
|
117
|
+
except Exception:
|
|
118
|
+
error = AppManagerError(
|
|
119
|
+
error_type=type(e).__name__,
|
|
120
|
+
title=str(e)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Return error response
|
|
124
|
+
error_reply = ManageAppReply(
|
|
125
|
+
apps=[],
|
|
126
|
+
error=error,
|
|
127
|
+
message_id=request.message_id
|
|
128
|
+
)
|
|
129
|
+
self.reply(error_reply)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
|
|
3
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
class MessageType(str, Enum):
|
|
10
|
+
"""Types of app manager messages."""
|
|
11
|
+
MANAGE_APP = "manage-app"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ManageAppRequest:
|
|
16
|
+
"""Request to manage apps."""
|
|
17
|
+
type: str = "manage-app"
|
|
18
|
+
jwt_token: Optional[str] = None
|
|
19
|
+
message_id: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class App:
|
|
23
|
+
"""App information."""
|
|
24
|
+
app_name: str
|
|
25
|
+
url: str
|
|
26
|
+
status: str
|
|
27
|
+
created_at: str
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class AppManagerError:
|
|
31
|
+
"""Error information for app manager operations."""
|
|
32
|
+
error_type: str
|
|
33
|
+
title: str
|
|
34
|
+
traceback: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_exception(cls, exc: Exception) -> 'AppManagerError':
|
|
38
|
+
return cls(
|
|
39
|
+
error_type=type(exc).__name__,
|
|
40
|
+
title=str(exc),
|
|
41
|
+
traceback=str(exc)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ManageAppReply:
|
|
46
|
+
"""Reply to a manage app request."""
|
|
47
|
+
type: str = "manage-app"
|
|
48
|
+
apps: List[App] = field(default_factory=list)
|
|
49
|
+
error: Optional[AppManagerError] = None
|
|
50
|
+
message_id: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class ErrorMessage:
|
|
54
|
+
"""Error message."""
|
|
55
|
+
error_type: str
|
|
56
|
+
title: str
|
|
57
|
+
traceback: Optional[str] = None
|
|
58
|
+
message_id: Optional[str] = None
|
|
@@ -38,7 +38,7 @@ class AgentExecutionHandler(CompletionHandler[AgentExecutionMetadata]):
|
|
|
38
38
|
display_prompt = metadata.input
|
|
39
39
|
|
|
40
40
|
# Add the prompt to the message history
|
|
41
|
-
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
|
|
41
|
+
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
|
|
42
42
|
new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
|
|
43
43
|
|
|
44
44
|
await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
|
|
@@ -47,7 +47,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
|
|
|
47
47
|
display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
|
|
48
48
|
|
|
49
49
|
# Add the prompt to the message history
|
|
50
|
-
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
|
|
50
|
+
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
|
|
51
51
|
new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
|
|
52
52
|
await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
|
|
53
53
|
|
|
@@ -110,7 +110,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
|
|
|
110
110
|
display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
|
|
111
111
|
|
|
112
112
|
# Add the prompt to the message history
|
|
113
|
-
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
|
|
113
|
+
new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
|
|
114
114
|
new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
|
|
115
115
|
await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
|
|
116
116
|
|
|
@@ -6,25 +6,33 @@ from mito_ai.completions.message_history import GlobalMessageHistory
|
|
|
6
6
|
from mito_ai.completions.models import ThreadID
|
|
7
7
|
from mito_ai.completions.providers import OpenAIProvider
|
|
8
8
|
from openai.types.chat import ChatCompletionMessageParam
|
|
9
|
-
from mito_ai.completions.prompt_builders.chat_system_message import
|
|
10
|
-
|
|
9
|
+
from mito_ai.completions.prompt_builders.chat_system_message import (
|
|
10
|
+
create_chat_system_message_prompt,
|
|
11
|
+
)
|
|
12
|
+
from mito_ai.completions.prompt_builders.agent_system_message import (
|
|
13
|
+
create_agent_system_message_prompt,
|
|
14
|
+
)
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
async def append_chat_system_message(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
message_history: GlobalMessageHistory,
|
|
19
|
+
model: str,
|
|
20
|
+
provider: OpenAIProvider,
|
|
21
|
+
thread_id: ThreadID,
|
|
17
22
|
) -> None:
|
|
18
|
-
|
|
23
|
+
|
|
19
24
|
# If the system message already exists, do nothing
|
|
20
|
-
if any(
|
|
25
|
+
if any(
|
|
26
|
+
msg["role"] == "system"
|
|
27
|
+
for msg in message_history.get_ai_optimized_history(thread_id)
|
|
28
|
+
):
|
|
21
29
|
return
|
|
22
|
-
|
|
30
|
+
|
|
23
31
|
system_message_prompt = create_chat_system_message_prompt()
|
|
24
32
|
|
|
25
33
|
system_message: ChatCompletionMessageParam = {
|
|
26
34
|
"role": "system",
|
|
27
|
-
"content": system_message_prompt
|
|
35
|
+
"content": system_message_prompt,
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
await message_history.append_message(
|
|
@@ -32,54 +40,86 @@ async def append_chat_system_message(
|
|
|
32
40
|
display_message=system_message,
|
|
33
41
|
model=model,
|
|
34
42
|
llm_provider=provider,
|
|
35
|
-
thread_id=thread_id
|
|
43
|
+
thread_id=thread_id,
|
|
36
44
|
)
|
|
37
45
|
|
|
46
|
+
|
|
38
47
|
async def append_agent_system_message(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
message_history: GlobalMessageHistory,
|
|
49
|
+
model: str,
|
|
50
|
+
provider: OpenAIProvider,
|
|
51
|
+
thread_id: ThreadID,
|
|
52
|
+
isChromeBrowser: bool,
|
|
44
53
|
) -> None:
|
|
45
|
-
|
|
54
|
+
|
|
46
55
|
# If the system message already exists, do nothing
|
|
47
|
-
if any(
|
|
56
|
+
if any(
|
|
57
|
+
msg["role"] == "system"
|
|
58
|
+
for msg in message_history.get_ai_optimized_history(thread_id)
|
|
59
|
+
):
|
|
48
60
|
return
|
|
49
|
-
|
|
61
|
+
|
|
50
62
|
system_message_prompt = create_agent_system_message_prompt(isChromeBrowser)
|
|
51
|
-
|
|
63
|
+
|
|
52
64
|
system_message: ChatCompletionMessageParam = {
|
|
53
65
|
"role": "system",
|
|
54
|
-
"content": system_message_prompt
|
|
66
|
+
"content": system_message_prompt,
|
|
55
67
|
}
|
|
56
|
-
|
|
68
|
+
|
|
57
69
|
await message_history.append_message(
|
|
58
70
|
ai_optimized_message=system_message,
|
|
59
71
|
display_message=system_message,
|
|
60
72
|
model=model,
|
|
61
73
|
llm_provider=provider,
|
|
62
|
-
thread_id=thread_id
|
|
74
|
+
thread_id=thread_id,
|
|
63
75
|
)
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_ai_optimized_message(
|
|
79
|
+
text: str,
|
|
80
|
+
base64EncodedActiveCellOutput: Optional[str] = None,
|
|
81
|
+
base64EncodedUploadedImage: Optional[str] = None,
|
|
82
|
+
) -> ChatCompletionMessageParam:
|
|
66
83
|
|
|
67
84
|
message_content: Union[str, List[Dict[str, Any]]]
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
has_uploaded_image = (
|
|
86
|
+
base64EncodedUploadedImage is not None and base64EncodedUploadedImage != ""
|
|
87
|
+
)
|
|
88
|
+
has_active_cell_output = (
|
|
89
|
+
base64EncodedActiveCellOutput is not None
|
|
90
|
+
and base64EncodedActiveCellOutput != ""
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if has_uploaded_image or has_active_cell_output:
|
|
94
|
+
message_content = [
|
|
70
95
|
{
|
|
71
96
|
"type": "text",
|
|
72
97
|
"text": text,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"type": "image_url",
|
|
76
|
-
"image_url": {"url": f"data:image/png;base64,{base64EncodedActiveCellOutput}"},
|
|
77
98
|
}
|
|
78
|
-
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
if has_uploaded_image:
|
|
102
|
+
message_content.append(
|
|
103
|
+
{
|
|
104
|
+
"type": "image_url",
|
|
105
|
+
"image_url": {
|
|
106
|
+
"url": f"data:image/png;base64,{base64EncodedUploadedImage}"
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if has_active_cell_output:
|
|
112
|
+
message_content.append(
|
|
113
|
+
{
|
|
114
|
+
"type": "image_url",
|
|
115
|
+
"image_url": {
|
|
116
|
+
"url": f"data:image/png;base64,{base64EncodedActiveCellOutput}"
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
)
|
|
79
120
|
else:
|
|
80
121
|
message_content = text
|
|
81
|
-
|
|
82
|
-
return cast(
|
|
83
|
-
"role": "user",
|
|
84
|
-
|
|
85
|
-
})
|
|
122
|
+
|
|
123
|
+
return cast(
|
|
124
|
+
ChatCompletionMessageParam, {"role": "user", "content": message_content}
|
|
125
|
+
)
|
mito_ai/completions/models.py
CHANGED
|
@@ -83,6 +83,7 @@ class ChatMessageMetadata():
|
|
|
83
83
|
variables: Optional[List[str]] = None
|
|
84
84
|
files: Optional[List[str]] = None
|
|
85
85
|
base64EncodedActiveCellOutput: Optional[str] = None
|
|
86
|
+
base64EncodedUploadedImage: Optional[str] = None
|
|
86
87
|
index: Optional[int] = None
|
|
87
88
|
stream: bool = False
|
|
88
89
|
additionalContext: Optional[List[Dict[str, str]]] = None
|
|
@@ -96,6 +97,7 @@ class AgentExecutionMetadata():
|
|
|
96
97
|
aiOptimizedCells: List[AIOptimizedCell]
|
|
97
98
|
isChromeBrowser: bool
|
|
98
99
|
base64EncodedActiveCellOutput: Optional[str] = None
|
|
100
|
+
base64EncodedUploadedImage: Optional[str] = None
|
|
99
101
|
variables: Optional[List[str]] = None
|
|
100
102
|
files: Optional[List[str]] = None
|
|
101
103
|
index: Optional[int] = None
|
|
@@ -38,6 +38,7 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
|
|
|
38
38
|
selected_variables = [context["value"] for context in additional_context if context.get("type") == "variable"]
|
|
39
39
|
selected_files = [context["value"] for context in additional_context if context.get("type") == "file"]
|
|
40
40
|
selected_db_connections = [context["value"] for context in additional_context if context.get("type") == "db"]
|
|
41
|
+
selected_images = [context["value"] for context in additional_context if context.get("type") == "img"]
|
|
41
42
|
|
|
42
43
|
# STEP 2: Create a list of strings (instructions) for each context type
|
|
43
44
|
context_parts = []
|
|
@@ -60,6 +61,12 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
|
|
|
60
61
|
+ "\n".join(selected_db_connections)
|
|
61
62
|
)
|
|
62
63
|
|
|
64
|
+
if len(selected_images) > 0:
|
|
65
|
+
context_parts.append(
|
|
66
|
+
"The following images have been selected by the user to be used in the task:\n"
|
|
67
|
+
+ "\n".join(selected_images)
|
|
68
|
+
)
|
|
69
|
+
|
|
63
70
|
# STEP 3: Combine into a single string
|
|
64
71
|
|
|
65
72
|
return "\n\n".join(context_parts)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
from mito_ai.completions.completion_handlers.utils import create_ai_optimized_message
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_text_only_message():
|
|
8
|
+
"""Test scenario where the user only inputs text"""
|
|
9
|
+
result = create_ai_optimized_message("Hello world")
|
|
10
|
+
|
|
11
|
+
assert result["role"] == "user"
|
|
12
|
+
assert result["content"] == "Hello world"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_message_with_uploaded_image():
|
|
16
|
+
"""Test scenario where the user uploads an image"""
|
|
17
|
+
result = create_ai_optimized_message(
|
|
18
|
+
text="Analyze this", base64EncodedUploadedImage="image_data"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
assert result["role"] == "user"
|
|
22
|
+
assert isinstance(result["content"], list)
|
|
23
|
+
assert result["content"][0]["type"] == "text"
|
|
24
|
+
assert result["content"][1]["type"] == "image_url"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_message_with_active_cell_output():
|
|
28
|
+
"""Test scenario where the active cell has an output"""
|
|
29
|
+
result = create_ai_optimized_message(
|
|
30
|
+
text="Analyze this", base64EncodedActiveCellOutput="cell_output_data"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert result["role"] == "user"
|
|
34
|
+
assert isinstance(result["content"], list)
|
|
35
|
+
assert result["content"][0]["type"] == "text"
|
|
36
|
+
assert result["content"][1]["type"] == "image_url"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_message_with_uploaded_image_and_active_cell_output():
|
|
40
|
+
"""Test scenario where the user uploads an image and the active cell has an output"""
|
|
41
|
+
result = create_ai_optimized_message(
|
|
42
|
+
text="Analyze this",
|
|
43
|
+
base64EncodedUploadedImage="image_data",
|
|
44
|
+
base64EncodedActiveCellOutput="cell_output_data",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert result["role"] == "user"
|
|
48
|
+
assert isinstance(result["content"], list)
|
|
49
|
+
assert result["content"][0]["type"] == "text"
|
|
50
|
+
assert result["content"][1]["type"] == "image_url"
|
|
51
|
+
assert result["content"][2]["type"] == "image_url"
|