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
|
@@ -0,0 +1,120 @@
|
|
|
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 os
|
|
6
|
+
import requests
|
|
7
|
+
import tempfile
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
import pytest
|
|
10
|
+
from mito_ai.tests.conftest import TOKEN
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def mock_user_json():
|
|
15
|
+
"""Fixture that creates a temporary user.json file with test data"""
|
|
16
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
17
|
+
# Create the .mito directory
|
|
18
|
+
mito_dir = os.path.join(temp_dir, ".mito")
|
|
19
|
+
os.makedirs(mito_dir, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
# Create a user.json file with test data
|
|
22
|
+
user_json_path = os.path.join(mito_dir, "user.json")
|
|
23
|
+
user_data = {
|
|
24
|
+
"user_email": "test@mail.com",
|
|
25
|
+
"static_user_id": "test_user_123",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
with open(user_json_path, "w") as f:
|
|
29
|
+
json.dump(user_data, f)
|
|
30
|
+
|
|
31
|
+
yield user_json_path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# --- GET USER KEY ---
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_get_user_with_mocked_data_success(
|
|
38
|
+
jp_base_url: str, mock_user_json: str
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Test successful GET user endpoint with mocked data"""
|
|
41
|
+
with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
|
|
42
|
+
response = requests.get(
|
|
43
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
44
|
+
headers={"Authorization": f"token {TOKEN}"},
|
|
45
|
+
)
|
|
46
|
+
assert response.status_code == 200
|
|
47
|
+
|
|
48
|
+
response_json = response.json()
|
|
49
|
+
assert response_json["key"] == "user_email"
|
|
50
|
+
assert response_json["value"] == "test@mail.com"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_get_user_with_mocked_data_not_found(
|
|
54
|
+
jp_base_url: str, mock_user_json: str
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Test GET user endpoint with mocked data for non-existent key"""
|
|
57
|
+
with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
|
|
58
|
+
response = requests.get(
|
|
59
|
+
jp_base_url + "/mito-ai/user/non_existent_key",
|
|
60
|
+
headers={"Authorization": f"token {TOKEN}"},
|
|
61
|
+
)
|
|
62
|
+
assert response.status_code == 404
|
|
63
|
+
|
|
64
|
+
response_json = response.json()
|
|
65
|
+
assert (
|
|
66
|
+
response_json["error"] == "User field with key 'non_existent_key' not found"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_get_user_with_no_auth(jp_base_url: str) -> None:
|
|
71
|
+
response = requests.get(
|
|
72
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
73
|
+
)
|
|
74
|
+
assert response.status_code == 403 # Forbidden
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_get_user_with_incorrect_auth(jp_base_url: str) -> None:
|
|
78
|
+
response = requests.get(
|
|
79
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
80
|
+
headers={"Authorization": f"token incorrect-token"},
|
|
81
|
+
)
|
|
82
|
+
assert response.status_code == 403 # Forbidden
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- PUT USER KEY ---
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_put_user_with_mocked_data_success(
|
|
89
|
+
jp_base_url: str, mock_user_json: str
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Test successful PUT user endpoint with mocked data"""
|
|
92
|
+
with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
|
|
93
|
+
response = requests.put(
|
|
94
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
95
|
+
headers={"Authorization": f"token {TOKEN}"},
|
|
96
|
+
json={"value": "jdoe@mail.com"},
|
|
97
|
+
)
|
|
98
|
+
assert response.status_code == 200
|
|
99
|
+
|
|
100
|
+
response_json = response.json()
|
|
101
|
+
assert response_json["status"] == "success"
|
|
102
|
+
assert response_json["key"] == "user_email"
|
|
103
|
+
assert response_json["value"] == "jdoe@mail.com"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_put_user_with_no_auth(jp_base_url: str) -> None:
|
|
107
|
+
response = requests.put(
|
|
108
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
109
|
+
json={"value": "jdoe@mail.com"},
|
|
110
|
+
)
|
|
111
|
+
assert response.status_code == 403 # Forbidden
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_put_user_with_incorrect_auth(jp_base_url: str) -> None:
|
|
115
|
+
response = requests.put(
|
|
116
|
+
jp_base_url + f"/mito-ai/user/user_email",
|
|
117
|
+
headers={"Authorization": f"token incorrect-token"},
|
|
118
|
+
json={"value": "jdoe@mail.com"},
|
|
119
|
+
)
|
|
120
|
+
assert response.status_code == 403 # Forbidden
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
import anthropic
|
|
6
6
|
from typing import List, Dict, Any, Tuple, Union, cast
|
|
7
7
|
from anthropic.types import MessageParam, ToolUnionParam, ToolParam
|
|
8
|
-
from mito_ai.utils.anthropic_utils import _prepare_anthropic_request_data_and_headers
|
|
8
|
+
from mito_ai.utils.anthropic_utils import ANTHROPIC_TIMEOUT, _prepare_anthropic_request_data_and_headers
|
|
9
9
|
from mito_ai.completions.models import MessageType
|
|
10
10
|
from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
|
|
11
11
|
from mito_ai.utils.db import get_user_field
|
|
@@ -35,7 +35,7 @@ def test_basic_request_preparation():
|
|
|
35
35
|
max_tokens = 100
|
|
36
36
|
temperature = 0.7
|
|
37
37
|
# Use NotGiven to ensure system is not included in inner_data
|
|
38
|
-
system = anthropic.
|
|
38
|
+
system = anthropic.Omit()
|
|
39
39
|
messages: List[MessageParam] = [{"role": "user", "content": "Hello"}]
|
|
40
40
|
message_type = MessageType.CHAT
|
|
41
41
|
|
|
@@ -52,7 +52,7 @@ def test_basic_request_preparation():
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
assert headers == {"Content-Type": "application/json"}
|
|
55
|
-
assert data["timeout"] ==
|
|
55
|
+
assert data["timeout"] == ANTHROPIC_TIMEOUT
|
|
56
56
|
assert data["max_retries"] == 1
|
|
57
57
|
assert data["email"] == "test@example.com"
|
|
58
58
|
assert data["user_id"] == "test_user_id"
|
|
@@ -106,7 +106,7 @@ def test_tools_and_tool_choice():
|
|
|
106
106
|
model="claude-3-sonnet",
|
|
107
107
|
max_tokens=100,
|
|
108
108
|
temperature=0.7,
|
|
109
|
-
system=anthropic.
|
|
109
|
+
system=anthropic.Omit(),
|
|
110
110
|
messages=[{"role": "user", "content": "Hello"}],
|
|
111
111
|
message_type=MessageType.CHAT,
|
|
112
112
|
tools=tools,
|
|
@@ -124,7 +124,7 @@ def test_stream_parameter():
|
|
|
124
124
|
model="claude-3-sonnet",
|
|
125
125
|
max_tokens=100,
|
|
126
126
|
temperature=0.7,
|
|
127
|
-
system=anthropic.
|
|
127
|
+
system=anthropic.Omit(),
|
|
128
128
|
messages=[{"role": "user", "content": "Hello"}],
|
|
129
129
|
message_type=MessageType.CHAT,
|
|
130
130
|
tools=None,
|
|
@@ -150,7 +150,7 @@ def test_missing_user_info(monkeypatch):
|
|
|
150
150
|
model="claude-3-sonnet",
|
|
151
151
|
max_tokens=100,
|
|
152
152
|
temperature=0.7,
|
|
153
|
-
system=anthropic.
|
|
153
|
+
system=anthropic.Omit(),
|
|
154
154
|
messages=[{"role": "user", "content": "Hello"}],
|
|
155
155
|
message_type=MessageType.CHAT,
|
|
156
156
|
tools=None,
|
mito_ai/user/handlers.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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 tornado
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
from jupyter_server.base.handlers import APIHandler
|
|
8
|
+
from mito_ai.utils.db import get_user_field, set_user_field
|
|
9
|
+
from mito_ai.utils.telemetry_utils import identify
|
|
10
|
+
from mito_ai.utils.version_utils import is_pro
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserHandler(APIHandler):
|
|
14
|
+
"""Handler for operations on a specific user"""
|
|
15
|
+
|
|
16
|
+
@tornado.web.authenticated
|
|
17
|
+
def get(self, key: str) -> None:
|
|
18
|
+
value: Optional[Any] = None
|
|
19
|
+
|
|
20
|
+
if key == "is_pro":
|
|
21
|
+
# Special case, since we don't store this key
|
|
22
|
+
# in the user.json file.
|
|
23
|
+
value = str(is_pro())
|
|
24
|
+
else:
|
|
25
|
+
value = get_user_field(key)
|
|
26
|
+
|
|
27
|
+
if value is None:
|
|
28
|
+
self.set_status(404)
|
|
29
|
+
self.finish(json.dumps({"error": f"User field with key '{key}' not found"}))
|
|
30
|
+
else:
|
|
31
|
+
self.finish(json.dumps({"key": key, "value": value}))
|
|
32
|
+
|
|
33
|
+
@tornado.web.authenticated
|
|
34
|
+
def put(self, key: str) -> None:
|
|
35
|
+
data = json.loads(self.request.body)
|
|
36
|
+
if "value" not in data:
|
|
37
|
+
self.set_status(400)
|
|
38
|
+
self.finish(json.dumps({"error": "Value is required"}))
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
set_user_field(key, data["value"])
|
|
42
|
+
identify() # Log the new user
|
|
43
|
+
self.finish(
|
|
44
|
+
json.dumps({"status": "success", "key": key, "value": data["value"]})
|
|
45
|
+
)
|
mito_ai/user/urls.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, List, Tuple
|
|
5
|
+
from jupyter_server.utils import url_path_join
|
|
6
|
+
from mito_ai.user.handlers import UserHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_user_urls(base_url: str) -> List[Tuple[str, Any, dict]]:
|
|
10
|
+
"""Get all user related URL patterns.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
base_url: The base URL for the Jupyter server
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
List of (url_pattern, handler_class, handler_kwargs) tuples
|
|
17
|
+
"""
|
|
18
|
+
BASE_URL = base_url + "/mito-ai"
|
|
19
|
+
return [
|
|
20
|
+
(url_path_join(BASE_URL, "user/(.*)"), UserHandler, {}),
|
|
21
|
+
]
|
mito_ai/utils/anthropic_utils.py
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
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
|
-
import asyncio
|
|
5
|
-
import json
|
|
6
|
-
import time
|
|
7
4
|
import anthropic
|
|
8
|
-
from typing import Any, Dict, List, Optional, Union, AsyncGenerator, Tuple, Callable
|
|
9
|
-
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from mito_ai.completions.models import AgentResponse, MessageType, ResponseFormatInfo, CompletionReply, CompletionStreamChunk
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union, AsyncGenerator, Tuple, Callable
|
|
6
|
+
from anthropic.types import MessageParam, TextBlockParam, ToolUnionParam
|
|
7
|
+
from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
|
|
8
|
+
from mito_ai.utils.provider_utils import does_message_require_fast_model
|
|
9
|
+
from mito_ai.completions.models import AgentResponse, MessageType, ResponseFormatInfo, CompletionReply, CompletionStreamChunk
|
|
13
10
|
from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
|
|
14
11
|
from mito_ai.utils.db import get_user_field
|
|
15
|
-
from mito_ai.utils.utils import is_running_test
|
|
16
|
-
from mito_ai.utils.server_limits import check_mito_server_quota
|
|
17
|
-
from .utils import _create_http_client
|
|
18
|
-
from tornado.httpclient import AsyncHTTPClient
|
|
19
12
|
from mito_ai.constants import MITO_ANTHROPIC_URL
|
|
20
13
|
|
|
21
14
|
__user_email: Optional[str] = None
|
|
22
15
|
__user_id: Optional[str] = None
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
ANTHROPIC_TIMEOUT = 60
|
|
25
18
|
max_retries = 1
|
|
26
|
-
|
|
19
|
+
|
|
20
|
+
FAST_ANTHROPIC_MODEL = "claude-3-5-haiku-latest"
|
|
27
21
|
|
|
28
22
|
def _prepare_anthropic_request_data_and_headers(
|
|
29
23
|
model: Union[str, None],
|
|
30
24
|
max_tokens: int,
|
|
31
25
|
temperature: float,
|
|
32
|
-
system: Union[str, anthropic.
|
|
26
|
+
system: Union[str, List[TextBlockParam], anthropic.Omit],
|
|
33
27
|
messages: List[MessageParam],
|
|
34
28
|
message_type: MessageType,
|
|
35
29
|
tools: Optional[List[ToolUnionParam]],
|
|
36
30
|
tool_choice: Optional[dict],
|
|
37
31
|
stream: Optional[bool]
|
|
38
32
|
) -> Tuple[Dict[str, Any], Dict[str, str]]:
|
|
39
|
-
|
|
33
|
+
|
|
40
34
|
global __user_email, __user_id
|
|
41
35
|
if __user_email is None:
|
|
42
36
|
__user_email = get_user_field(UJ_USER_EMAIL)
|
|
@@ -47,10 +41,11 @@ def _prepare_anthropic_request_data_and_headers(
|
|
|
47
41
|
"model": model,
|
|
48
42
|
"max_tokens": max_tokens,
|
|
49
43
|
"temperature": temperature,
|
|
50
|
-
"messages": messages
|
|
44
|
+
"messages": messages,
|
|
45
|
+
"betas": ["context-1m-2025-08-07"]
|
|
51
46
|
}
|
|
52
|
-
# Add system to inner_data only if it is not anthropic.
|
|
53
|
-
if not isinstance(system, anthropic.
|
|
47
|
+
# Add system to inner_data only if it is not anthropic.Omit
|
|
48
|
+
if not isinstance(system, anthropic.Omit):
|
|
54
49
|
inner_data["system"] = system
|
|
55
50
|
if tools:
|
|
56
51
|
inner_data["tools"] = tools
|
|
@@ -60,7 +55,7 @@ def _prepare_anthropic_request_data_and_headers(
|
|
|
60
55
|
inner_data["stream"] = stream
|
|
61
56
|
# Compose the outer data dict
|
|
62
57
|
data = {
|
|
63
|
-
"timeout":
|
|
58
|
+
"timeout": ANTHROPIC_TIMEOUT,
|
|
64
59
|
"max_retries": max_retries,
|
|
65
60
|
"email": __user_email,
|
|
66
61
|
"user_id": __user_id,
|
|
@@ -73,42 +68,31 @@ async def get_anthropic_completion_from_mito_server(
|
|
|
73
68
|
model: Union[str, None],
|
|
74
69
|
max_tokens: int,
|
|
75
70
|
temperature: float,
|
|
76
|
-
system: Union[str, anthropic.
|
|
71
|
+
system: Union[str, anthropic.Omit],
|
|
77
72
|
messages: List[MessageParam],
|
|
78
73
|
tools: Optional[List[ToolUnionParam]],
|
|
79
74
|
tool_choice: Optional[dict],
|
|
80
75
|
message_type: MessageType
|
|
81
|
-
) ->
|
|
76
|
+
) -> str:
|
|
82
77
|
data, headers = _prepare_anthropic_request_data_and_headers(
|
|
83
78
|
model, max_tokens, temperature, system, messages, message_type, tools, tool_choice, None
|
|
84
79
|
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
print(f"Anthropic request completed in {time.time() - start_time:.2f} seconds")
|
|
96
|
-
except Exception as e:
|
|
97
|
-
print(f"Anthropic request failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
|
|
98
|
-
raise
|
|
99
|
-
finally:
|
|
100
|
-
http_client.close()
|
|
101
|
-
content = json.loads(res.body)
|
|
102
|
-
# If the response is wrapped in a 'data' field, extract it
|
|
103
|
-
if isinstance(content, dict) and "data" in content:
|
|
104
|
-
return cast(Message, content["data"])
|
|
105
|
-
return cast(Message, content)
|
|
80
|
+
|
|
81
|
+
return await get_response_from_mito_server(
|
|
82
|
+
MITO_ANTHROPIC_URL,
|
|
83
|
+
headers,
|
|
84
|
+
data,
|
|
85
|
+
ANTHROPIC_TIMEOUT,
|
|
86
|
+
max_retries,
|
|
87
|
+
message_type,
|
|
88
|
+
provider_name="Claude"
|
|
89
|
+
)
|
|
106
90
|
|
|
107
91
|
async def stream_anthropic_completion_from_mito_server(
|
|
108
92
|
model: Union[str, None],
|
|
109
93
|
max_tokens: int,
|
|
110
94
|
temperature: float,
|
|
111
|
-
system: Union[str, anthropic.
|
|
95
|
+
system: Union[str, List[TextBlockParam], anthropic.Omit],
|
|
112
96
|
messages: List[MessageParam],
|
|
113
97
|
stream: bool,
|
|
114
98
|
message_type: MessageType,
|
|
@@ -118,91 +102,46 @@ async def stream_anthropic_completion_from_mito_server(
|
|
|
118
102
|
data, headers = _prepare_anthropic_request_data_and_headers(
|
|
119
103
|
model, max_tokens, temperature, system, messages, message_type, None, None, stream
|
|
120
104
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
async def wait_for_fetch() -> None:
|
|
142
|
-
try:
|
|
143
|
-
await fetch_future
|
|
144
|
-
nonlocal fetch_complete
|
|
145
|
-
fetch_complete = True
|
|
146
|
-
print("Anthropic fetch completed")
|
|
147
|
-
except Exception as e:
|
|
148
|
-
print(f"Error in Anthropic fetch: {str(e)}")
|
|
149
|
-
raise
|
|
150
|
-
fetch_task = asyncio.create_task(wait_for_fetch())
|
|
151
|
-
while not (fetch_complete and chunk_queue.empty()):
|
|
152
|
-
try:
|
|
153
|
-
chunk = await asyncio.wait_for(chunk_queue.get(), timeout=0.1)
|
|
154
|
-
if reply_fn and message_id:
|
|
155
|
-
reply_fn(CompletionStreamChunk(
|
|
156
|
-
parent_id=message_id,
|
|
157
|
-
chunk=CompletionItem(
|
|
158
|
-
content=chunk,
|
|
159
|
-
isIncomplete=True,
|
|
160
|
-
token=message_id,
|
|
161
|
-
),
|
|
162
|
-
done=False,
|
|
163
|
-
))
|
|
164
|
-
yield chunk
|
|
165
|
-
except asyncio.TimeoutError:
|
|
166
|
-
if fetch_complete and chunk_queue.empty():
|
|
167
|
-
break
|
|
168
|
-
continue
|
|
169
|
-
print(f"\nAnthropic stream completed in {time.time() - start_time:.2f} seconds")
|
|
170
|
-
if reply_fn and message_id:
|
|
171
|
-
reply_fn(CompletionStreamChunk(
|
|
172
|
-
parent_id=message_id,
|
|
173
|
-
chunk=CompletionItem(
|
|
174
|
-
content="",
|
|
175
|
-
isIncomplete=False,
|
|
176
|
-
token=message_id,
|
|
177
|
-
),
|
|
178
|
-
done=True,
|
|
179
|
-
))
|
|
180
|
-
except Exception as e:
|
|
181
|
-
print(f"\nAnthropic stream failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
|
|
182
|
-
if fetch_future:
|
|
183
|
-
try:
|
|
184
|
-
await fetch_future
|
|
185
|
-
except Exception:
|
|
186
|
-
pass
|
|
187
|
-
raise
|
|
188
|
-
finally:
|
|
189
|
-
http_client.close()
|
|
190
|
-
|
|
105
|
+
|
|
106
|
+
# Use the unified streaming function
|
|
107
|
+
# If the reply_fn and message_id are empty, this function still handles those requests. This is particularly needed for the streamlit dashboard functionality
|
|
108
|
+
actual_reply_fn = reply_fn if reply_fn is not None else (lambda x: None)
|
|
109
|
+
actual_message_id = message_id if message_id is not None else ""
|
|
110
|
+
async for chunk in stream_response_from_mito_server(
|
|
111
|
+
url=MITO_ANTHROPIC_URL,
|
|
112
|
+
headers=headers,
|
|
113
|
+
data=data,
|
|
114
|
+
timeout=ANTHROPIC_TIMEOUT,
|
|
115
|
+
max_retries=max_retries,
|
|
116
|
+
message_type=message_type,
|
|
117
|
+
reply_fn=actual_reply_fn,
|
|
118
|
+
message_id=actual_message_id,
|
|
119
|
+
chunk_processor=None,
|
|
120
|
+
provider_name="Claude",
|
|
121
|
+
):
|
|
122
|
+
yield chunk
|
|
123
|
+
|
|
124
|
+
|
|
191
125
|
def get_anthropic_completion_function_params(
|
|
126
|
+
message_type: MessageType,
|
|
192
127
|
model: str,
|
|
193
128
|
messages: List[MessageParam],
|
|
194
129
|
max_tokens: int,
|
|
195
|
-
system: Union[str, anthropic.
|
|
130
|
+
system: Union[str, List[TextBlockParam], anthropic.Omit],
|
|
196
131
|
temperature: float = 0.0,
|
|
197
132
|
tools: Optional[List[ToolUnionParam]] = None,
|
|
198
133
|
tool_choice: Optional[dict] = None,
|
|
199
134
|
stream: Optional[bool] = None,
|
|
200
135
|
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
201
|
-
) -> Dict[str, Any]:
|
|
136
|
+
) -> Dict[str, Any]:
|
|
202
137
|
"""
|
|
203
138
|
Build the provider_data dict for Anthropic completions, mirroring the OpenAI approach.
|
|
204
139
|
Only includes fields needed for the Anthropic API.
|
|
205
140
|
"""
|
|
141
|
+
|
|
142
|
+
message_requires_fast_model = does_message_require_fast_model(message_type)
|
|
143
|
+
model = FAST_ANTHROPIC_MODEL if message_requires_fast_model else model
|
|
144
|
+
|
|
206
145
|
provider_data = {
|
|
207
146
|
"model": model,
|
|
208
147
|
"max_tokens": max_tokens,
|
|
@@ -210,11 +149,6 @@ def get_anthropic_completion_function_params(
|
|
|
210
149
|
"messages": messages,
|
|
211
150
|
"system": system,
|
|
212
151
|
}
|
|
213
|
-
if response_format_info is not None:
|
|
214
|
-
# TODO: This should not be here.. the model is resolved in the anthropic client.
|
|
215
|
-
# This also means that chat is using the fast model...
|
|
216
|
-
# I bet the same bug exists in gemini...
|
|
217
|
-
provider_data["model"] = INLINE_COMPLETION_MODEL
|
|
218
152
|
if tools:
|
|
219
153
|
provider_data["tools"] = tools
|
|
220
154
|
if response_format_info and response_format_info.name == "agent_response":
|
mito_ai/utils/create.py
CHANGED
|
@@ -36,6 +36,17 @@ def is_user_json_exists_and_valid_json() -> bool:
|
|
|
36
36
|
except:
|
|
37
37
|
return False
|
|
38
38
|
|
|
39
|
+
def get_temp_user_id() -> Optional[str]:
|
|
40
|
+
"""
|
|
41
|
+
Looks for a temporary user ID, generated by the desktop app.
|
|
42
|
+
"""
|
|
43
|
+
temp_user_id_path = os.path.join(MITO_FOLDER, 'temp_user_id.txt')
|
|
44
|
+
|
|
45
|
+
if os.path.exists(temp_user_id_path):
|
|
46
|
+
with open(temp_user_id_path, 'r') as f:
|
|
47
|
+
return f.read()
|
|
48
|
+
|
|
49
|
+
return None
|
|
39
50
|
|
|
40
51
|
def try_create_user_json_file() -> None:
|
|
41
52
|
|
|
@@ -50,7 +61,12 @@ def try_create_user_json_file() -> None:
|
|
|
50
61
|
with open(USER_JSON_PATH, 'w+') as f:
|
|
51
62
|
f.write(json.dumps(USER_JSON_DEFAULT))
|
|
52
63
|
|
|
53
|
-
#
|
|
64
|
+
# Next, look for a temp user id
|
|
65
|
+
temp_user_id = get_temp_user_id()
|
|
66
|
+
if temp_user_id:
|
|
67
|
+
set_user_field(UJ_STATIC_USER_ID, temp_user_id)
|
|
68
|
+
|
|
69
|
+
# Finally, we take special care to put all the testing/CI environments
|
|
54
70
|
# (e.g. Github actions) under one ID and email
|
|
55
71
|
if is_running_test():
|
|
56
72
|
set_user_field(UJ_STATIC_USER_ID, GITHUB_ACTION_ID)
|
|
@@ -0,0 +1,42 @@
|
|
|
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.app_deploy.models import AppDeployError
|
|
5
|
+
|
|
6
|
+
class MitoAppError(Exception):
|
|
7
|
+
"""Exception raised for custom error in the application."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, error_code: int) -> None:
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.message = message
|
|
12
|
+
self.error_code = error_code
|
|
13
|
+
|
|
14
|
+
class StreamlitPreviewError(MitoAppError):
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return f"[PreviewError]: {self.message} (Error Code: {self.error_code})"
|
|
17
|
+
|
|
18
|
+
class StreamlitConversionError(MitoAppError):
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
return f"[ConversionError]: {self.message} (Error Code: {self.error_code})"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StreamlitDeploymentError(MitoAppError):
|
|
24
|
+
"""Raised when a deployment operation fails."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, error: AppDeployError):
|
|
27
|
+
self.error = error
|
|
28
|
+
self.error_type = error.error_type
|
|
29
|
+
self.message_id = getattr(error, "message_id", "ErrorMessageID")
|
|
30
|
+
self.error_code = getattr(error, "error_code", 500)
|
|
31
|
+
self.hint = getattr(error, "hint", "")
|
|
32
|
+
self.traceback = getattr(error, "traceback", "")
|
|
33
|
+
self.error_type = getattr(error, "error_type", "Error")
|
|
34
|
+
self.message = error.message
|
|
35
|
+
print(f"self_message: {self.message}")
|
|
36
|
+
super().__init__(self.message, self.error_code)
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
base = f"[DeploymentError]: {self.message} (Error Code: {self.error_code})"
|
|
40
|
+
if self.hint:
|
|
41
|
+
base += f"\nHint: {self.hint}"
|
|
42
|
+
return base
|