mito-ai 0.1.41__py3-none-any.whl → 0.1.43__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 +134 -0
- mito_ai/app_manager/models.py +57 -0
- mito_ai/app_manager/utils.py +24 -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 +99 -37
- mito_ai/completions/prompt_builders/utils.py +7 -1
- mito_ai/file_uploads/handlers.py +49 -26
- mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
- mito_ai/tests/file_uploads/test_handlers.py +15 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
- {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.41.data → mito_ai-0.1.43.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.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js +1324 -247
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js.map +1 -0
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
- mito_ai-0.1.43.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.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js +23 -23
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js.map +1 -0
- mito_ai-0.1.43.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.43.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.43.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.43.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.43.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.43.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.43.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.43.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.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
- mito_ai-0.1.43.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.43.dist-info}/METADATA +1 -1
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/RECORD +46 -41
- 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.43.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.43.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.43.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.41.data → mito_ai-0.1.43.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.43.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.43.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.43.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.43.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.43.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.43.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from mito_ai.completions.completion_handlers.utils import (
|
|
9
|
+
create_ai_optimized_message,
|
|
10
|
+
extract_and_encode_images_from_additional_context,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@contextmanager
|
|
15
|
+
def temporary_image_file(suffix=".png", content=b"fake_image_data"):
|
|
16
|
+
"""Context manager that creates a temporary image file for testing."""
|
|
17
|
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
|
18
|
+
temp_file.write(content)
|
|
19
|
+
temp_file_path = temp_file.name
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
yield temp_file_path
|
|
23
|
+
finally:
|
|
24
|
+
# Clean up the temporary file
|
|
25
|
+
os.unlink(temp_file_path)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_text_only_message():
|
|
29
|
+
"""Test scenario where the user only inputs text"""
|
|
30
|
+
result = create_ai_optimized_message("Hello world")
|
|
31
|
+
|
|
32
|
+
assert result["role"] == "user"
|
|
33
|
+
assert result["content"] == "Hello world"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_message_with_uploaded_image():
|
|
37
|
+
"""Test scenario where the user uploads an image"""
|
|
38
|
+
with temporary_image_file() as temp_file_path:
|
|
39
|
+
result = create_ai_optimized_message(
|
|
40
|
+
text="Analyze this",
|
|
41
|
+
additional_context=[{"type": "image/png", "value": temp_file_path}],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert result["role"] == "user"
|
|
45
|
+
assert isinstance(result["content"], list)
|
|
46
|
+
assert result["content"][0]["type"] == "text"
|
|
47
|
+
assert result["content"][1]["type"] == "image_url"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_message_with_multiple_uploaded_images():
|
|
51
|
+
"""Test scenario where the user uploads multiple images"""
|
|
52
|
+
with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
|
|
53
|
+
with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
|
|
54
|
+
result = create_ai_optimized_message(
|
|
55
|
+
text="Analyze these images",
|
|
56
|
+
additional_context=[
|
|
57
|
+
{"type": "image/png", "value": temp_file1},
|
|
58
|
+
{"type": "image/jpeg", "value": temp_file2},
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
assert result["role"] == "user"
|
|
63
|
+
assert isinstance(result["content"], list)
|
|
64
|
+
assert len(result["content"]) == 3 # text + 2 images
|
|
65
|
+
assert result["content"][0]["type"] == "text"
|
|
66
|
+
assert result["content"][0]["text"] == "Analyze these images"
|
|
67
|
+
assert result["content"][1]["type"] == "image_url"
|
|
68
|
+
assert result["content"][2]["type"] == "image_url"
|
|
69
|
+
|
|
70
|
+
# Verify the image URLs are properly formatted
|
|
71
|
+
assert result["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
|
|
72
|
+
assert result["content"][2]["image_url"]["url"].startswith("data:image/jpeg;base64,")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_message_with_active_cell_output():
|
|
76
|
+
"""Test scenario where the active cell has an output"""
|
|
77
|
+
result = create_ai_optimized_message(
|
|
78
|
+
text="Analyze this", base64EncodedActiveCellOutput="cell_output_data"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert result["role"] == "user"
|
|
82
|
+
assert isinstance(result["content"], list)
|
|
83
|
+
assert result["content"][0]["type"] == "text"
|
|
84
|
+
assert result["content"][1]["type"] == "image_url"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_message_with_uploaded_image_and_active_cell_output():
|
|
88
|
+
"""Test scenario where the user uploads an image and the active cell has an output"""
|
|
89
|
+
with temporary_image_file() as temp_file_path:
|
|
90
|
+
result = create_ai_optimized_message(
|
|
91
|
+
text="Analyze this",
|
|
92
|
+
additional_context=[{"type": "image/png", "value": temp_file_path}],
|
|
93
|
+
base64EncodedActiveCellOutput="cell_output_data",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert result["role"] == "user"
|
|
97
|
+
assert isinstance(result["content"], list)
|
|
98
|
+
assert result["content"][0]["type"] == "text"
|
|
99
|
+
assert result["content"][1]["type"] == "image_url"
|
|
100
|
+
assert result["content"][2]["type"] == "image_url"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_extract_and_encode_images_from_additional_context_valid_image():
|
|
104
|
+
"""Test extracting and encoding a valid image file"""
|
|
105
|
+
with temporary_image_file() as temp_file_path:
|
|
106
|
+
additional_context = [{"type": "image/png", "value": temp_file_path}]
|
|
107
|
+
|
|
108
|
+
encoded_images = extract_and_encode_images_from_additional_context(
|
|
109
|
+
additional_context
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
assert len(encoded_images) == 1
|
|
113
|
+
assert encoded_images[0].startswith("data:image/png;base64,")
|
|
114
|
+
# Verify it's valid base64 by checking it can be decoded
|
|
115
|
+
base64_data = encoded_images[0].split(",")[1]
|
|
116
|
+
decoded_data = base64.b64decode(base64_data)
|
|
117
|
+
assert decoded_data == b"fake_image_data"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_extract_and_encode_images_from_additional_context_multiple_images():
|
|
121
|
+
"""Test extracting and encoding multiple image files"""
|
|
122
|
+
with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
|
|
123
|
+
with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
|
|
124
|
+
additional_context = [
|
|
125
|
+
{"type": "image/png", "value": temp_file1},
|
|
126
|
+
{"type": "image/jpeg", "value": temp_file2},
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
encoded_images = extract_and_encode_images_from_additional_context(
|
|
130
|
+
additional_context
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert len(encoded_images) == 2
|
|
134
|
+
assert encoded_images[0].startswith("data:image/png;base64,")
|
|
135
|
+
assert encoded_images[1].startswith("data:image/jpeg;base64,")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_extract_and_encode_images_from_additional_context_invalid_file():
|
|
139
|
+
"""Test handling of invalid/non-existent image files"""
|
|
140
|
+
additional_context = [{"type": "image/png", "value": "non_existent_file.png"}]
|
|
141
|
+
|
|
142
|
+
encoded_images = extract_and_encode_images_from_additional_context(
|
|
143
|
+
additional_context
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert len(encoded_images) == 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_extract_and_encode_images_from_additional_context_non_image_types():
|
|
150
|
+
"""Test that non-image types are ignored"""
|
|
151
|
+
with temporary_image_file(suffix=".txt", content=b"text_data") as temp_file:
|
|
152
|
+
additional_context = [
|
|
153
|
+
{"type": "text/plain", "value": temp_file},
|
|
154
|
+
{"type": "application/pdf", "value": "document.pdf"},
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
encoded_images = extract_and_encode_images_from_additional_context(
|
|
158
|
+
additional_context
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
assert len(encoded_images) == 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_extract_and_encode_images_from_additional_context_mixed_types():
|
|
165
|
+
"""Test handling of mixed image and non-image types"""
|
|
166
|
+
with temporary_image_file() as temp_image_file:
|
|
167
|
+
additional_context = [
|
|
168
|
+
{"type": "image/png", "value": temp_image_file},
|
|
169
|
+
{"type": "text/plain", "value": "document.txt"},
|
|
170
|
+
{"type": "image/jpeg", "value": "non_existent.jpg"},
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
encoded_images = extract_and_encode_images_from_additional_context(
|
|
174
|
+
additional_context
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Should only have the valid PNG image
|
|
178
|
+
assert len(encoded_images) == 1
|
|
179
|
+
assert encoded_images[0].startswith("data:image/png;base64,")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_extract_and_encode_images_from_additional_context_empty():
|
|
183
|
+
"""Test handling of empty or None additional_context"""
|
|
184
|
+
# Test with None
|
|
185
|
+
encoded_images = extract_and_encode_images_from_additional_context(None)
|
|
186
|
+
assert len(encoded_images) == 0
|
|
187
|
+
|
|
188
|
+
# Test with empty list
|
|
189
|
+
encoded_images = extract_and_encode_images_from_additional_context([])
|
|
190
|
+
assert len(encoded_images) == 0
|
|
@@ -265,3 +265,18 @@ def test_save_chunk(handler, temp_dir):
|
|
|
265
265
|
|
|
266
266
|
# Clean up
|
|
267
267
|
del handler._temp_dirs[filename]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_image_size_limit_exceeded(handler, temp_dir):
|
|
271
|
+
"""Test that image uploads exceeding 3MB are rejected."""
|
|
272
|
+
filename = "large_image.jpg"
|
|
273
|
+
# Create 5MB of data (5 * 1024 * 1024 bytes)
|
|
274
|
+
file_data = b"x" * (5 * 1024 * 1024)
|
|
275
|
+
notebook_dir = temp_dir
|
|
276
|
+
|
|
277
|
+
# The _handle_regular_upload should raise a ValueError for oversized images
|
|
278
|
+
with pytest.raises(ValueError) as exc_info:
|
|
279
|
+
handler._handle_regular_upload(filename, file_data, notebook_dir)
|
|
280
|
+
|
|
281
|
+
# Verify the error message mentions the size limit
|
|
282
|
+
assert "exceeded 3MB limit" in str(exc_info.value)
|