mito-ai 0.1.33__py3-none-any.whl → 0.1.49__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mito_ai/__init__.py +49 -9
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +142 -67
- mito_ai/{app_builder → app_deploy}/__init__.py +1 -1
- mito_ai/app_deploy/app_deploy_utils.py +44 -0
- mito_ai/app_deploy/handlers.py +345 -0
- mito_ai/{app_builder → app_deploy}/models.py +35 -22
- mito_ai/app_manager/__init__.py +4 -0
- mito_ai/app_manager/handlers.py +167 -0
- mito_ai/app_manager/models.py +71 -0
- mito_ai/app_manager/utils.py +24 -0
- mito_ai/auth/README.md +18 -0
- mito_ai/auth/__init__.py +6 -0
- mito_ai/auth/handlers.py +96 -0
- mito_ai/auth/urls.py +13 -0
- mito_ai/chat_history/handlers.py +63 -0
- mito_ai/chat_history/urls.py +32 -0
- mito_ai/completions/completion_handlers/agent_execution_handler.py +1 -1
- mito_ai/completions/completion_handlers/chat_completion_handler.py +4 -4
- mito_ai/completions/completion_handlers/utils.py +99 -37
- mito_ai/completions/handlers.py +57 -20
- mito_ai/completions/message_history.py +9 -1
- mito_ai/completions/models.py +31 -7
- mito_ai/completions/prompt_builders/agent_execution_prompt.py +21 -2
- mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +8 -0
- mito_ai/completions/prompt_builders/agent_system_message.py +115 -42
- mito_ai/completions/prompt_builders/chat_name_prompt.py +6 -6
- mito_ai/completions/prompt_builders/chat_prompt.py +18 -11
- mito_ai/completions/prompt_builders/chat_system_message.py +4 -0
- mito_ai/completions/prompt_builders/prompt_constants.py +23 -4
- mito_ai/completions/prompt_builders/utils.py +72 -10
- mito_ai/completions/providers.py +81 -47
- mito_ai/constants.py +25 -24
- mito_ai/file_uploads/__init__.py +3 -0
- mito_ai/file_uploads/handlers.py +248 -0
- mito_ai/file_uploads/urls.py +21 -0
- mito_ai/gemini_client.py +44 -48
- mito_ai/log/handlers.py +10 -3
- mito_ai/log/urls.py +3 -3
- mito_ai/openai_client.py +30 -44
- mito_ai/path_utils.py +70 -0
- mito_ai/streamlit_conversion/agent_utils.py +37 -0
- mito_ai/streamlit_conversion/prompts/prompt_constants.py +172 -0
- mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
- mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +46 -0
- mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
- mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +45 -0
- mito_ai/streamlit_conversion/prompts/streamlit_system_prompt.py +56 -0
- mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
- mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +144 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +85 -0
- mito_ai/streamlit_conversion/validate_streamlit_app.py +105 -0
- mito_ai/streamlit_preview/__init__.py +6 -0
- mito_ai/streamlit_preview/handlers.py +111 -0
- mito_ai/streamlit_preview/manager.py +152 -0
- mito_ai/streamlit_preview/urls.py +22 -0
- mito_ai/streamlit_preview/utils.py +29 -0
- mito_ai/tests/chat_history/test_chat_history.py +211 -0
- mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
- mito_ai/tests/deploy_app/test_app_deploy_utils.py +89 -0
- mito_ai/tests/file_uploads/__init__.py +2 -0
- mito_ai/tests/file_uploads/test_handlers.py +282 -0
- mito_ai/tests/message_history/test_generate_short_chat_name.py +0 -4
- mito_ai/tests/message_history/test_message_history_utils.py +103 -23
- mito_ai/tests/open_ai_utils_test.py +18 -22
- mito_ai/tests/providers/test_anthropic_client.py +447 -0
- mito_ai/tests/providers/test_azure.py +2 -6
- mito_ai/tests/providers/test_capabilities.py +120 -0
- mito_ai/tests/{test_gemini_client.py → providers/test_gemini_client.py} +40 -36
- mito_ai/tests/providers/test_mito_server_utils.py +448 -0
- mito_ai/tests/providers/test_model_resolution.py +130 -0
- mito_ai/tests/providers/test_openai_client.py +57 -0
- mito_ai/tests/providers/test_provider_completion_exception.py +66 -0
- mito_ai/tests/providers/test_provider_limits.py +42 -0
- mito_ai/tests/providers/test_providers.py +382 -0
- mito_ai/tests/providers/test_retry_logic.py +389 -0
- mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
- mito_ai/tests/providers/utils.py +85 -0
- mito_ai/tests/streamlit_conversion/__init__.py +3 -0
- mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +246 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +193 -0
- mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +112 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +118 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +292 -0
- mito_ai/tests/test_constants.py +31 -3
- mito_ai/tests/test_telemetry.py +12 -0
- mito_ai/tests/user/__init__.py +2 -0
- mito_ai/tests/user/test_user.py +120 -0
- mito_ai/tests/utils/test_anthropic_utils.py +6 -6
- mito_ai/user/handlers.py +45 -0
- mito_ai/user/urls.py +21 -0
- mito_ai/utils/anthropic_utils.py +55 -121
- mito_ai/utils/create.py +17 -1
- mito_ai/utils/error_classes.py +42 -0
- mito_ai/utils/gemini_utils.py +39 -94
- mito_ai/utils/message_history_utils.py +7 -4
- mito_ai/utils/mito_server_utils.py +242 -0
- mito_ai/utils/open_ai_utils.py +38 -155
- mito_ai/utils/provider_utils.py +49 -0
- mito_ai/utils/server_limits.py +1 -1
- mito_ai/utils/telemetry_utils.py +137 -5
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +102 -100
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/package.json +4 -2
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +3 -1
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +2 -2
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +15948 -8403
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js +58 -33
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js.map +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +10 -2
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js +533 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +6941 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +1021 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +59698 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js +7440 -0
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js.map +1 -0
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
- mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/METADATA +5 -2
- mito_ai-0.1.49.dist-info/RECORD +205 -0
- mito_ai/app_builder/handlers.py +0 -218
- mito_ai/tests/providers_test.py +0 -438
- mito_ai/tests/test_anthropic_client.py +0 -270
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js +0 -7842
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js.map +0 -1
- mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
- mito_ai-0.1.33.dist-info/RECORD +0 -134
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/licenses/LICENSE +0 -0
mito_ai/__init__.py
CHANGED
|
@@ -5,12 +5,33 @@ from typing import List, Dict
|
|
|
5
5
|
from jupyter_server.utils import url_path_join
|
|
6
6
|
from mito_ai.completions.handlers import CompletionHandler
|
|
7
7
|
from mito_ai.completions.providers import OpenAIProvider
|
|
8
|
-
from mito_ai.
|
|
8
|
+
from mito_ai.completions.message_history import GlobalMessageHistory
|
|
9
|
+
from mito_ai.app_deploy.handlers import AppDeployHandler
|
|
10
|
+
from mito_ai.streamlit_preview.handlers import StreamlitPreviewHandler
|
|
9
11
|
from mito_ai.log.urls import get_log_urls
|
|
10
12
|
from mito_ai.version_check import VersionCheckHandler
|
|
11
13
|
from mito_ai.db.urls import get_db_urls
|
|
12
14
|
from mito_ai.settings.urls import get_settings_urls
|
|
13
15
|
from mito_ai.rules.urls import get_rules_urls
|
|
16
|
+
from mito_ai.auth.urls import get_auth_urls
|
|
17
|
+
from mito_ai.streamlit_preview.urls import get_streamlit_preview_urls
|
|
18
|
+
from mito_ai.app_manager.handlers import AppManagerHandler
|
|
19
|
+
from mito_ai.file_uploads.urls import get_file_uploads_urls
|
|
20
|
+
from mito_ai.user.urls import get_user_urls
|
|
21
|
+
from mito_ai.chat_history.urls import get_chat_history_urls
|
|
22
|
+
|
|
23
|
+
# Force Matplotlib to use the Jupyter inline backend.
|
|
24
|
+
# Background: importing Streamlit sets os.environ["MPLBACKEND"] = "Agg" very early.
|
|
25
|
+
# In a Jupyter kernel, that selects a non‑interactive canvas and can trigger:
|
|
26
|
+
# "UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown"
|
|
27
|
+
# which prevents figures from rendering in notebook outputs.
|
|
28
|
+
# We preempt this by selecting the canonical Jupyter inline backend BEFORE any
|
|
29
|
+
# Matplotlib import, so figures render inline reliably. This must run very early.
|
|
30
|
+
# See: https://github.com/streamlit/streamlit/issues/9640
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline"
|
|
34
|
+
|
|
14
35
|
try:
|
|
15
36
|
from _version import __version__
|
|
16
37
|
except ImportError:
|
|
@@ -44,31 +65,50 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
|
|
|
44
65
|
base_url = web_app.settings["base_url"]
|
|
45
66
|
|
|
46
67
|
open_ai_provider = OpenAIProvider(config=server_app.config)
|
|
68
|
+
|
|
69
|
+
# Create a single GlobalMessageHistory instance for the entire server
|
|
70
|
+
# This ensures thread-safe access to the .mito/ai-chats directory
|
|
71
|
+
global_message_history = GlobalMessageHistory()
|
|
47
72
|
|
|
48
73
|
# WebSocket handlers
|
|
49
74
|
handlers = [
|
|
50
75
|
(
|
|
51
76
|
url_path_join(base_url, "mito-ai", "completions"),
|
|
52
77
|
CompletionHandler,
|
|
53
|
-
{"llm": open_ai_provider},
|
|
78
|
+
{"llm": open_ai_provider, "message_history": global_message_history},
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
url_path_join(base_url, "mito-ai", "app-deploy"),
|
|
82
|
+
AppDeployHandler,
|
|
83
|
+
{}
|
|
54
84
|
),
|
|
55
85
|
(
|
|
56
|
-
url_path_join(base_url, "mito-ai", "
|
|
57
|
-
|
|
86
|
+
url_path_join(base_url, "mito-ai", "streamlit-preview"),
|
|
87
|
+
StreamlitPreviewHandler,
|
|
58
88
|
{}
|
|
59
89
|
),
|
|
60
90
|
(
|
|
61
91
|
url_path_join(base_url, "mito-ai", "version-check"),
|
|
62
92
|
VersionCheckHandler,
|
|
63
93
|
{},
|
|
94
|
+
),
|
|
95
|
+
(
|
|
96
|
+
url_path_join(base_url, "mito-ai", "app-manager"),
|
|
97
|
+
AppManagerHandler,
|
|
98
|
+
{}
|
|
64
99
|
)
|
|
65
100
|
]
|
|
66
101
|
|
|
67
102
|
# 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))
|
|
72
|
-
|
|
103
|
+
handlers.extend(get_db_urls(base_url)) # type: ignore
|
|
104
|
+
handlers.extend(get_settings_urls(base_url)) # type: ignore
|
|
105
|
+
handlers.extend(get_rules_urls(base_url)) # type: ignore
|
|
106
|
+
handlers.extend(get_log_urls(base_url, open_ai_provider.key_type)) # type: ignore
|
|
107
|
+
handlers.extend(get_auth_urls(base_url)) # type: ignore
|
|
108
|
+
handlers.extend(get_streamlit_preview_urls(base_url)) # type: ignore
|
|
109
|
+
handlers.extend(get_file_uploads_urls(base_url)) # type: ignore
|
|
110
|
+
handlers.extend(get_user_urls(base_url)) # type: ignore
|
|
111
|
+
handlers.extend(get_chat_history_urls(base_url, global_message_history)) # type: ignore
|
|
112
|
+
|
|
73
113
|
web_app.add_handlers(host_pattern, handlers)
|
|
74
114
|
server_app.log.info("Loaded the mito_ai server extension")
|
mito_ai/_version.py
CHANGED
mito_ai/anthropic_client.py
CHANGED
|
@@ -5,8 +5,9 @@ import json
|
|
|
5
5
|
import anthropic
|
|
6
6
|
from typing import Dict, Any, Optional, Tuple, Union, Callable, List, cast
|
|
7
7
|
|
|
8
|
-
from anthropic.types import Message, MessageParam
|
|
8
|
+
from anthropic.types import Message, MessageParam, TextBlockParam
|
|
9
9
|
from mito_ai.completions.models import ResponseFormatInfo, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
|
|
10
|
+
from mito_ai.constants import MESSAGE_HISTORY_TRIM_THRESHOLD
|
|
10
11
|
from openai.types.chat import ChatCompletionMessageParam
|
|
11
12
|
from mito_ai.utils.anthropic_utils import get_anthropic_completion_from_mito_server, stream_anthropic_completion_from_mito_server, get_anthropic_completion_function_params
|
|
12
13
|
|
|
@@ -15,8 +16,6 @@ from mito_ai.utils.anthropic_utils import get_anthropic_completion_from_mito_ser
|
|
|
15
16
|
# 8192 is the maximum allowed number of output tokens for claude-3-5-haiku-20241022
|
|
16
17
|
MAX_TOKENS = 8_000
|
|
17
18
|
|
|
18
|
-
ANTHROPIC_FAST_MODEL = "claude-3-5-haiku-latest"
|
|
19
|
-
|
|
20
19
|
def extract_and_parse_anthropic_json_response(response: Message) -> Union[object, Any]:
|
|
21
20
|
"""
|
|
22
21
|
Extracts and parses the JSON response from the Claude API.
|
|
@@ -53,12 +52,12 @@ def extract_and_parse_anthropic_json_response(response: Message) -> Union[object
|
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
def get_anthropic_system_prompt_and_messages(messages: List[ChatCompletionMessageParam]) -> Tuple[
|
|
56
|
-
Union[str, anthropic.
|
|
55
|
+
Union[str, anthropic.Omit], List[MessageParam]]:
|
|
57
56
|
"""
|
|
58
57
|
Convert a list of OpenAI messages to a list of Anthropic messages.
|
|
59
58
|
"""
|
|
60
59
|
|
|
61
|
-
system_prompt: Union[str, anthropic.
|
|
60
|
+
system_prompt: Union[str, anthropic.Omit] = anthropic.Omit()
|
|
62
61
|
anthropic_messages: List[MessageParam] = []
|
|
63
62
|
|
|
64
63
|
for message in messages:
|
|
@@ -126,14 +125,97 @@ def get_anthropic_system_prompt_and_messages(messages: List[ChatCompletionMessag
|
|
|
126
125
|
return system_prompt, anthropic_messages
|
|
127
126
|
|
|
128
127
|
|
|
128
|
+
def add_cache_control_to_message(message: MessageParam) -> MessageParam:
|
|
129
|
+
"""
|
|
130
|
+
Adds cache_control to a message's content.
|
|
131
|
+
Handles both string content and list of content blocks.
|
|
132
|
+
"""
|
|
133
|
+
content = message.get("content")
|
|
134
|
+
|
|
135
|
+
if isinstance(content, str):
|
|
136
|
+
# Simple string content - convert to list format with cache_control
|
|
137
|
+
return {
|
|
138
|
+
"role": message["role"],
|
|
139
|
+
"content": [
|
|
140
|
+
{
|
|
141
|
+
"type": "text",
|
|
142
|
+
"text": content,
|
|
143
|
+
"cache_control": {"type": "ephemeral"}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
elif isinstance(content, list) and len(content) > 0:
|
|
149
|
+
# List of content blocks - add cache_control to last block
|
|
150
|
+
content_blocks = content.copy()
|
|
151
|
+
last_block = content_blocks[-1].copy()
|
|
152
|
+
last_block["cache_control"] = {"type": "ephemeral"}
|
|
153
|
+
content_blocks[-1] = last_block
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"role": message["role"],
|
|
157
|
+
"content": content_blocks
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
# Edge case: empty or malformed content
|
|
162
|
+
return message
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_anthropic_system_prompt_and_messages_with_caching(messages: List[ChatCompletionMessageParam]) -> Tuple[
|
|
166
|
+
Union[str, List[TextBlockParam], anthropic.Omit], List[MessageParam]]:
|
|
167
|
+
"""
|
|
168
|
+
Convert a list of OpenAI messages to a list of Anthropic messages with caching applied.
|
|
169
|
+
|
|
170
|
+
Caching Strategy:
|
|
171
|
+
1. System prompt (static) → Always cached
|
|
172
|
+
2. Stable conversation history → Cache at keep_recent boundary
|
|
173
|
+
3. Recent messages → Never cached (always fresh)
|
|
174
|
+
|
|
175
|
+
The keep_recent parameter determines which messages are stable and won't be trimmed.
|
|
176
|
+
We cache at the keep_recent boundary because those messages are guaranteed to be stable.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# Get the base system prompt and messages
|
|
180
|
+
system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
|
|
181
|
+
|
|
182
|
+
# 1. Cache the system prompt always
|
|
183
|
+
# If the system prompt is something like anthropic.Omit, we don't need to cache it
|
|
184
|
+
cached_system_prompt: Union[str, List[TextBlockParam], anthropic.Omit] = system_prompt
|
|
185
|
+
if isinstance(system_prompt, str):
|
|
186
|
+
cached_system_prompt = [{
|
|
187
|
+
"type": "text",
|
|
188
|
+
"text": system_prompt,
|
|
189
|
+
"cache_control": {"type": "ephemeral"}
|
|
190
|
+
}]
|
|
191
|
+
|
|
192
|
+
# 2. Cache conversation history at the boundary where the messages are stable.
|
|
193
|
+
# Messages are stable after they are more than MESSAGE_HISTORY_TRIM_THRESHOLD old.
|
|
194
|
+
# At this point, the messages are not edited anymore, so they will not invalidate the cache.
|
|
195
|
+
# If we included the messages before the boundary in the cache, then every time we send a new
|
|
196
|
+
# message, we would invalidate the cache and we would never get a cache hit except for the system prompt.
|
|
197
|
+
messages_with_cache = []
|
|
198
|
+
|
|
199
|
+
if len(anthropic_messages) > 0:
|
|
200
|
+
cache_boundary = len(anthropic_messages) - MESSAGE_HISTORY_TRIM_THRESHOLD - 1
|
|
201
|
+
|
|
202
|
+
# Add all messages, but only add cache_control to the message at the boundary
|
|
203
|
+
for i, msg in enumerate(anthropic_messages):
|
|
204
|
+
if i == cache_boundary:
|
|
205
|
+
messages_with_cache.append(add_cache_control_to_message(msg))
|
|
206
|
+
else:
|
|
207
|
+
messages_with_cache.append(msg)
|
|
208
|
+
|
|
209
|
+
return cached_system_prompt, messages_with_cache
|
|
210
|
+
|
|
211
|
+
|
|
129
212
|
class AnthropicClient:
|
|
130
213
|
"""
|
|
131
214
|
A client for interacting with the Anthropic API or the Mito server fallback.
|
|
132
215
|
"""
|
|
133
216
|
|
|
134
|
-
def __init__(self, api_key: Optional[str],
|
|
217
|
+
def __init__(self, api_key: Optional[str], timeout: int = 30, max_retries: int = 1):
|
|
135
218
|
self.api_key = api_key
|
|
136
|
-
self.model = model
|
|
137
219
|
self.timeout = timeout
|
|
138
220
|
self.max_retries = max_retries
|
|
139
221
|
self.client: Optional[anthropic.Anthropic]
|
|
@@ -142,66 +224,66 @@ class AnthropicClient:
|
|
|
142
224
|
else:
|
|
143
225
|
self.client = None
|
|
144
226
|
|
|
145
|
-
async def request_completions(
|
|
146
|
-
|
|
147
|
-
|
|
227
|
+
async def request_completions(
|
|
228
|
+
self, messages: List[ChatCompletionMessageParam],
|
|
229
|
+
model: str,
|
|
230
|
+
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
231
|
+
message_type: MessageType = MessageType.CHAT
|
|
232
|
+
) -> Any:
|
|
148
233
|
"""
|
|
149
234
|
Get a response from Claude or the Mito server that adheres to the AgentResponse format.
|
|
150
235
|
"""
|
|
151
|
-
|
|
152
|
-
|
|
236
|
+
anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
|
|
237
|
+
|
|
238
|
+
provider_data = get_anthropic_completion_function_params(
|
|
239
|
+
message_type=message_type,
|
|
240
|
+
model=model,
|
|
241
|
+
messages=anthropic_messages,
|
|
242
|
+
max_tokens=MAX_TOKENS,
|
|
243
|
+
temperature=0,
|
|
244
|
+
system=anthropic_system_prompt,
|
|
245
|
+
stream=None,
|
|
246
|
+
response_format_info=response_format_info
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if self.api_key:
|
|
250
|
+
# Unpack provider_data for direct API call
|
|
251
|
+
assert self.client is not None
|
|
252
|
+
response = self.client.messages.create(**provider_data)
|
|
153
253
|
|
|
154
|
-
provider_data
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
max_tokens=MAX_TOKENS,
|
|
158
|
-
temperature=0,
|
|
159
|
-
system=anthropic_system_prompt,
|
|
160
|
-
stream=None,
|
|
161
|
-
response_format_info=response_format_info
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if self.api_key:
|
|
165
|
-
# Unpack provider_data for direct API call
|
|
166
|
-
assert self.client is not None
|
|
167
|
-
response = self.client.messages.create(**provider_data)
|
|
168
|
-
if provider_data.get("tool_choice") is not None:
|
|
169
|
-
result = extract_and_parse_anthropic_json_response(response)
|
|
170
|
-
return json.dumps(result) if not isinstance(result, str) else result
|
|
171
|
-
else:
|
|
172
|
-
content = response.content
|
|
173
|
-
if content[0].type == "text":
|
|
174
|
-
return content[0].text
|
|
175
|
-
else:
|
|
176
|
-
return ""
|
|
254
|
+
if provider_data.get("tool_choice") is not None:
|
|
255
|
+
result = extract_and_parse_anthropic_json_response(response)
|
|
256
|
+
return json.dumps(result) if not isinstance(result, str) else result
|
|
177
257
|
else:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
258
|
+
content = response.content
|
|
259
|
+
if content[0].type == "text":
|
|
260
|
+
return content[0].text
|
|
261
|
+
else:
|
|
262
|
+
return ""
|
|
263
|
+
else:
|
|
264
|
+
# Only pass provider_data to the server
|
|
265
|
+
response = await get_anthropic_completion_from_mito_server(
|
|
266
|
+
model=provider_data["model"],
|
|
267
|
+
max_tokens=provider_data["max_tokens"],
|
|
268
|
+
temperature=provider_data["temperature"],
|
|
269
|
+
system=provider_data["system"],
|
|
270
|
+
messages=provider_data["messages"],
|
|
271
|
+
tools=provider_data.get("tools"),
|
|
272
|
+
tool_choice=provider_data.get("tool_choice"),
|
|
273
|
+
message_type=message_type
|
|
274
|
+
)
|
|
275
|
+
return response
|
|
194
276
|
|
|
195
|
-
async def
|
|
277
|
+
async def stream_completions(self, messages: List[ChatCompletionMessageParam], model: str, message_id: str, message_type: MessageType,
|
|
196
278
|
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None]) -> str:
|
|
197
279
|
try:
|
|
198
|
-
anthropic_system_prompt, anthropic_messages =
|
|
280
|
+
anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
|
|
199
281
|
accumulated_response = ""
|
|
200
282
|
|
|
201
283
|
if self.api_key:
|
|
202
284
|
assert self.client is not None
|
|
203
285
|
stream = self.client.messages.create(
|
|
204
|
-
model=
|
|
286
|
+
model=model,
|
|
205
287
|
max_tokens=MAX_TOKENS,
|
|
206
288
|
temperature=0,
|
|
207
289
|
system=anthropic_system_prompt,
|
|
@@ -209,7 +291,6 @@ class AnthropicClient:
|
|
|
209
291
|
stream=True
|
|
210
292
|
)
|
|
211
293
|
|
|
212
|
-
|
|
213
294
|
for chunk in stream:
|
|
214
295
|
if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
|
|
215
296
|
content = chunk.delta.text
|
|
@@ -229,24 +310,17 @@ class AnthropicClient:
|
|
|
229
310
|
|
|
230
311
|
else:
|
|
231
312
|
async for stram_chunk in stream_anthropic_completion_from_mito_server(
|
|
232
|
-
model=
|
|
313
|
+
model=model,
|
|
233
314
|
max_tokens=MAX_TOKENS,
|
|
234
315
|
temperature=0,
|
|
235
316
|
system=anthropic_system_prompt,
|
|
236
317
|
messages=anthropic_messages,
|
|
237
318
|
stream=True,
|
|
238
|
-
message_type=message_type
|
|
319
|
+
message_type=message_type,
|
|
320
|
+
reply_fn=reply_fn,
|
|
321
|
+
message_id=message_id
|
|
239
322
|
):
|
|
240
323
|
accumulated_response += stram_chunk
|
|
241
|
-
reply_fn(CompletionStreamChunk(
|
|
242
|
-
parent_id=message_id,
|
|
243
|
-
chunk=CompletionItem(
|
|
244
|
-
content=stram_chunk,
|
|
245
|
-
isIncomplete=True,
|
|
246
|
-
token=message_id,
|
|
247
|
-
),
|
|
248
|
-
done=False,
|
|
249
|
-
))
|
|
250
324
|
|
|
251
325
|
return accumulated_response
|
|
252
326
|
|
|
@@ -254,6 +328,7 @@ class AnthropicClient:
|
|
|
254
328
|
raise Exception("Rate limit exceeded. Please try again later or reduce your request frequency.")
|
|
255
329
|
|
|
256
330
|
except Exception as e:
|
|
257
|
-
|
|
331
|
+
print(f"Error streaming content: {str(e)}")
|
|
332
|
+
raise e
|
|
258
333
|
|
|
259
334
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import zipfile
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from mito_ai.path_utils import AbsoluteNotebookDirPath
|
|
10
|
+
|
|
11
|
+
def add_files_to_zip(
|
|
12
|
+
zip_path: str,
|
|
13
|
+
notebook_dir_path: AbsoluteNotebookDirPath,
|
|
14
|
+
files_to_add: List[str],
|
|
15
|
+
app_file_name: str,
|
|
16
|
+
logger: Optional[logging.Logger] = None
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Create a zip file at zip_path and add the selected files/folders."""
|
|
19
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
20
|
+
for file_to_add_rel_path in files_to_add:
|
|
21
|
+
|
|
22
|
+
file_to_add_abs_path = os.path.join(notebook_dir_path, file_to_add_rel_path)
|
|
23
|
+
|
|
24
|
+
if os.path.isfile(file_to_add_abs_path):
|
|
25
|
+
basename = os.path.basename(file_to_add_abs_path)
|
|
26
|
+
|
|
27
|
+
if basename == app_file_name:
|
|
28
|
+
# For the actual app file, we want to write it just as app.py
|
|
29
|
+
# so our infra can always deploy using `streamlit run app.py`
|
|
30
|
+
# without having to account for different app names
|
|
31
|
+
zipf.write(file_to_add_abs_path, arcname='app.py')
|
|
32
|
+
else:
|
|
33
|
+
# otherwise we want to keep the name as is so all references
|
|
34
|
+
# to it from the app are correct
|
|
35
|
+
zipf.write(file_to_add_abs_path, arcname=file_to_add_rel_path)
|
|
36
|
+
elif os.path.isdir(file_to_add_abs_path):
|
|
37
|
+
for root, _, files in os.walk(file_to_add_abs_path):
|
|
38
|
+
for file in files:
|
|
39
|
+
file_abs = os.path.join(root, file)
|
|
40
|
+
arcname = os.path.relpath(file_abs, notebook_dir_path)
|
|
41
|
+
zipf.write(file_abs, arcname=arcname)
|
|
42
|
+
else:
|
|
43
|
+
if logger:
|
|
44
|
+
logger.warning(f"Skipping missing file: {file_to_add_abs_path}")
|