mito-ai 0.1.32__py3-none-any.whl → 0.1.34__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/_version.py +1 -1
- mito_ai/anthropic_client.py +52 -54
- mito_ai/app_builder/handlers.py +2 -4
- mito_ai/completions/models.py +15 -1
- mito_ai/completions/prompt_builders/agent_system_message.py +10 -2
- mito_ai/completions/providers.py +79 -39
- mito_ai/constants.py +11 -24
- mito_ai/gemini_client.py +44 -48
- mito_ai/openai_client.py +30 -44
- mito_ai/tests/message_history/test_generate_short_chat_name.py +0 -4
- mito_ai/tests/open_ai_utils_test.py +18 -22
- mito_ai/tests/{test_anthropic_client.py → providers/test_anthropic_client.py} +37 -32
- 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/utils.py +85 -0
- mito_ai/tests/test_constants.py +15 -2
- mito_ai/tests/test_telemetry.py +12 -0
- mito_ai/utils/anthropic_utils.py +21 -29
- mito_ai/utils/gemini_utils.py +18 -22
- mito_ai/utils/mito_server_utils.py +92 -0
- mito_ai/utils/open_ai_utils.py +22 -46
- mito_ai/utils/provider_utils.py +49 -0
- mito_ai/utils/telemetry_utils.py +11 -1
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.42b54cf8f038cc526980.js → mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js +785 -351
- mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js.map +1 -0
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.a711c58b58423173bd24.js → mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.51d07439b02aaa830975.js +13 -16
- mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.51d07439b02aaa830975.js.map +1 -0
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js → mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js +6 -2
- mito_ai-0.1.34.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js.map +1 -0
- {mito_ai-0.1.32.dist-info → mito_ai-0.1.34.dist-info}/METADATA +1 -1
- {mito_ai-0.1.32.dist-info → mito_ai-0.1.34.dist-info}/RECORD +52 -43
- mito_ai/tests/providers_test.py +0 -438
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.42b54cf8f038cc526980.js.map +0 -1
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.a711c58b58423173bd24.js.map +0 -1
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js.map +0 -1
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js +0 -7842
- mito_ai-0.1.32.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js.map +0 -1
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.32.data → mito_ai-0.1.34.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.32.dist-info → mito_ai-0.1.34.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.32.dist-info → mito_ai-0.1.34.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.32.dist-info → mito_ai-0.1.34.dist-info}/licenses/LICENSE +0 -0
mito_ai/_version.py
CHANGED
mito_ai/anthropic_client.py
CHANGED
|
@@ -6,7 +6,8 @@ import anthropic
|
|
|
6
6
|
from typing import Dict, Any, Optional, Tuple, Union, Callable, List, cast
|
|
7
7
|
|
|
8
8
|
from anthropic.types import Message, MessageParam
|
|
9
|
-
from mito_ai.completions.models import ResponseFormatInfo, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
|
|
9
|
+
from mito_ai.completions.models import CompletionError, ResponseFormatInfo, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
|
|
10
|
+
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
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.
|
|
@@ -131,9 +130,8 @@ class AnthropicClient:
|
|
|
131
130
|
A client for interacting with the Anthropic API or the Mito server fallback.
|
|
132
131
|
"""
|
|
133
132
|
|
|
134
|
-
def __init__(self, api_key: Optional[str],
|
|
133
|
+
def __init__(self, api_key: Optional[str], timeout: int = 30, max_retries: int = 1):
|
|
135
134
|
self.api_key = api_key
|
|
136
|
-
self.model = model
|
|
137
135
|
self.timeout = timeout
|
|
138
136
|
self.max_retries = max_retries
|
|
139
137
|
self.client: Optional[anthropic.Anthropic]
|
|
@@ -142,57 +140,56 @@ class AnthropicClient:
|
|
|
142
140
|
else:
|
|
143
141
|
self.client = None
|
|
144
142
|
|
|
145
|
-
async def request_completions(
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
async def request_completions(
|
|
144
|
+
self, messages: List[ChatCompletionMessageParam],
|
|
145
|
+
model: str,
|
|
146
|
+
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
147
|
+
message_type: MessageType = MessageType.CHAT
|
|
148
|
+
) -> Any:
|
|
148
149
|
"""
|
|
149
150
|
Get a response from Claude or the Mito server that adheres to the AgentResponse format.
|
|
150
151
|
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
else:
|
|
172
|
-
content = response.content
|
|
173
|
-
if content[0].type == "text":
|
|
174
|
-
return content[0].text
|
|
175
|
-
else:
|
|
176
|
-
return ""
|
|
152
|
+
anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
|
|
153
|
+
|
|
154
|
+
provider_data = get_anthropic_completion_function_params(
|
|
155
|
+
message_type=message_type,
|
|
156
|
+
model=model,
|
|
157
|
+
messages=anthropic_messages,
|
|
158
|
+
max_tokens=MAX_TOKENS,
|
|
159
|
+
temperature=0,
|
|
160
|
+
system=anthropic_system_prompt,
|
|
161
|
+
stream=None,
|
|
162
|
+
response_format_info=response_format_info
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if self.api_key:
|
|
166
|
+
# Unpack provider_data for direct API call
|
|
167
|
+
assert self.client is not None
|
|
168
|
+
response = self.client.messages.create(**provider_data)
|
|
169
|
+
if provider_data.get("tool_choice") is not None:
|
|
170
|
+
result = extract_and_parse_anthropic_json_response(response)
|
|
171
|
+
return json.dumps(result) if not isinstance(result, str) else result
|
|
177
172
|
else:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
173
|
+
content = response.content
|
|
174
|
+
if content[0].type == "text":
|
|
175
|
+
return content[0].text
|
|
176
|
+
else:
|
|
177
|
+
return ""
|
|
178
|
+
else:
|
|
179
|
+
# Only pass provider_data to the server
|
|
180
|
+
response = await get_anthropic_completion_from_mito_server(
|
|
181
|
+
model=provider_data["model"],
|
|
182
|
+
max_tokens=provider_data["max_tokens"],
|
|
183
|
+
temperature=provider_data["temperature"],
|
|
184
|
+
system=provider_data["system"],
|
|
185
|
+
messages=provider_data["messages"],
|
|
186
|
+
tools=provider_data.get("tools"),
|
|
187
|
+
tool_choice=provider_data.get("tool_choice"),
|
|
188
|
+
message_type=message_type
|
|
189
|
+
)
|
|
190
|
+
return response
|
|
194
191
|
|
|
195
|
-
async def
|
|
192
|
+
async def stream_completions(self, messages: List[ChatCompletionMessageParam], model: str, message_id: str, message_type: MessageType,
|
|
196
193
|
reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None]) -> str:
|
|
197
194
|
try:
|
|
198
195
|
anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
|
|
@@ -201,7 +198,7 @@ class AnthropicClient:
|
|
|
201
198
|
if self.api_key:
|
|
202
199
|
assert self.client is not None
|
|
203
200
|
stream = self.client.messages.create(
|
|
204
|
-
model=
|
|
201
|
+
model=model,
|
|
205
202
|
max_tokens=MAX_TOKENS,
|
|
206
203
|
temperature=0,
|
|
207
204
|
system=anthropic_system_prompt,
|
|
@@ -229,7 +226,7 @@ class AnthropicClient:
|
|
|
229
226
|
|
|
230
227
|
else:
|
|
231
228
|
async for stram_chunk in stream_anthropic_completion_from_mito_server(
|
|
232
|
-
model=
|
|
229
|
+
model=model,
|
|
233
230
|
max_tokens=MAX_TOKENS,
|
|
234
231
|
temperature=0,
|
|
235
232
|
system=anthropic_system_prompt,
|
|
@@ -254,6 +251,7 @@ class AnthropicClient:
|
|
|
254
251
|
raise Exception("Rate limit exceeded. Please try again later or reduce your request frequency.")
|
|
255
252
|
|
|
256
253
|
except Exception as e:
|
|
257
|
-
|
|
254
|
+
print(f"Error streaming content: {str(e)}")
|
|
255
|
+
raise e
|
|
258
256
|
|
|
259
257
|
|
mito_ai/app_builder/handlers.py
CHANGED
|
@@ -16,11 +16,9 @@ from mito_ai.app_builder.models import (
|
|
|
16
16
|
MessageType
|
|
17
17
|
)
|
|
18
18
|
from mito_ai.logger import get_logger
|
|
19
|
+
from mito_ai.constants import ACTIVE_STREAMLIT_BASE_URL
|
|
19
20
|
import requests
|
|
20
21
|
|
|
21
|
-
# API endpoint for getting pre-signed URL
|
|
22
|
-
API_BASE_URL = "https://fr12uvtfy5.execute-api.us-east-1.amazonaws.com"
|
|
23
|
-
|
|
24
22
|
class AppBuilderHandler(BaseWebSocketHandler):
|
|
25
23
|
"""Handler for app building requests."""
|
|
26
24
|
|
|
@@ -155,7 +153,7 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
155
153
|
try:
|
|
156
154
|
# Step 1: Get pre-signed URL from API
|
|
157
155
|
self.log.info("Getting pre-signed upload URL...")
|
|
158
|
-
url_response = requests.get(f"{
|
|
156
|
+
url_response = requests.get(f"{ACTIVE_STREAMLIT_BASE_URL}/get-upload-url?app_name={app_name}")
|
|
159
157
|
url_response.raise_for_status()
|
|
160
158
|
|
|
161
159
|
url_data = url_response.json()
|
mito_ai/completions/models.py
CHANGED
|
@@ -8,6 +8,7 @@ from openai.types.chat import ChatCompletionMessageParam
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
# The ThreadID is the unique identifier for the chat thread.
|
|
12
13
|
ThreadID = NewType('ThreadID', str)
|
|
13
14
|
|
|
@@ -20,6 +21,7 @@ class CellUpdate(BaseModel):
|
|
|
20
21
|
index: Optional[int]
|
|
21
22
|
id: Optional[str]
|
|
22
23
|
code: str
|
|
24
|
+
code_summary: str
|
|
23
25
|
cell_type: Optional[Literal['code', 'markdown']]
|
|
24
26
|
|
|
25
27
|
|
|
@@ -225,6 +227,19 @@ class CompletionError:
|
|
|
225
227
|
While mypy doesn't know about this attribute on BaseException, we need to handle it
|
|
226
228
|
to properly extract error messages from OpenAI API responses.
|
|
227
229
|
"""
|
|
230
|
+
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# Handle ProviderCompletionException specially
|
|
234
|
+
if isinstance(exception, ProviderCompletionException):
|
|
235
|
+
return CompletionError(
|
|
236
|
+
error_type="LLM Provider Error",
|
|
237
|
+
title=exception.user_friendly_title,
|
|
238
|
+
traceback=traceback.format_exc(),
|
|
239
|
+
hint=exception.user_friendly_hint
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Handle all other exceptions as before
|
|
228
243
|
error_type = type(exception)
|
|
229
244
|
error_module = getattr(error_type, "__module__", "")
|
|
230
245
|
|
|
@@ -249,7 +264,6 @@ class CompletionError:
|
|
|
249
264
|
hint=hint,
|
|
250
265
|
)
|
|
251
266
|
|
|
252
|
-
|
|
253
267
|
@dataclass(frozen=True)
|
|
254
268
|
class ErrorMessage(CompletionError):
|
|
255
269
|
"""
|
|
@@ -49,6 +49,7 @@ Format:
|
|
|
49
49
|
type: 'modification'
|
|
50
50
|
id: str,
|
|
51
51
|
code: str
|
|
52
|
+
code_summary: str
|
|
52
53
|
cell_type: 'code' | 'markdown'
|
|
53
54
|
}}
|
|
54
55
|
get_cell_output_cell_id: None,
|
|
@@ -59,7 +60,8 @@ Important information:
|
|
|
59
60
|
1. The id is the id of the code cell that you want to update. The id MUST already be part of the original Jupyter Notebook that your colleague shared with you.
|
|
60
61
|
2. The message is a short summary of your thought process that helped you decide what to update in cell_update.
|
|
61
62
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
62
|
-
4.
|
|
63
|
+
4. The code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
64
|
+
5. Important: Only use the CELL_UPDATE tool if you want to add/modify a notebook cell in response to the user's request. If the user is just sending you a friendly greeting or asking you a question about yourself, you SHOULD NOT USE A CELL_UPDATE tool because it does not require modifying the notebook. Instead, just use the FINISHED_TASK response.
|
|
63
65
|
|
|
64
66
|
#### Cell Addition:
|
|
65
67
|
When you want to add a new cell to the notebook, respond in this format
|
|
@@ -72,6 +74,7 @@ Format:
|
|
|
72
74
|
type: 'new'
|
|
73
75
|
index: int
|
|
74
76
|
code: str
|
|
77
|
+
code_summary: str
|
|
75
78
|
cell_type: 'code' | 'markdown'
|
|
76
79
|
}}
|
|
77
80
|
get_cell_output_cell_id: None,
|
|
@@ -82,7 +85,8 @@ Important information:
|
|
|
82
85
|
1. The index should be the 0-index position of where you want the new code cell to be added in the notebook.
|
|
83
86
|
2. The message is a short summary of your thought process that helped you decide what to update in cell_update.
|
|
84
87
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
85
|
-
4.
|
|
88
|
+
4. code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
89
|
+
5. The cell_type should only be 'markdown' if there is no code to add. There may be times where the code has comments. These are still code cells and should have the cell_type 'code'. Any cells that are labeled 'markdown' will be converted to markdown cells by the user.
|
|
86
90
|
|
|
87
91
|
<Cell Modification Example>
|
|
88
92
|
Jupyter Notebook:
|
|
@@ -126,6 +130,7 @@ Output:
|
|
|
126
130
|
type: 'modification'
|
|
127
131
|
id: 'c68fdf19-db8c-46dd-926f-d90ad35bb3bc',
|
|
128
132
|
code: "import pandas as pd\\nsales_df = pd.read_csv('./sales.csv')\\nloan_multiplier = 1.5\\nsales_df['transaction_date'] = pd.to_datetime(sales_df['transaction_date'])\\nsales_df['total_price'] = sales_df['total_price'] * sales_multiplier",
|
|
133
|
+
code_summary: "Converting the transaction_date column",
|
|
129
134
|
cell_type: 'code'
|
|
130
135
|
}},
|
|
131
136
|
get_cell_output_cell_id: None,
|
|
@@ -175,6 +180,8 @@ Output:
|
|
|
175
180
|
type: 'add'
|
|
176
181
|
index: 2
|
|
177
182
|
code: "import matplotlib.pyplot as plt\n\nplt.bar(sales_df.index, sales_df['total_price'])\nplt.title('Total Price per Sale')\nplt.xlabel('Transaction Number')\nplt.ylabel('Sales Price ($)')\nplt.show()"
|
|
183
|
+
code_summary: "Plotting total_price",
|
|
184
|
+
code_summary: "Plotting total_price"
|
|
178
185
|
}},
|
|
179
186
|
get_cell_output_cell_id: None,
|
|
180
187
|
next_steps: None
|
|
@@ -309,6 +316,7 @@ Output:
|
|
|
309
316
|
type: 'add'
|
|
310
317
|
index: 2
|
|
311
318
|
code: "all_time_high_row_idx = tesla_stock_prices_df['closing_price'].idxmax()\nall_time_high_date = tesla_stock_prices_df.at[all_time_high_row_idx, 'Date']\nall_time_high_price = tesla_stock_prices_df.at[all_time_high_row_idx, 'closing_price']"
|
|
319
|
+
code_summary: "Calculating all time high"
|
|
312
320
|
}},
|
|
313
321
|
get_cell_output_cell_id: None
|
|
314
322
|
}}
|
mito_ai/completions/providers.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
|
+
import asyncio
|
|
5
6
|
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
|
6
7
|
from mito_ai import constants
|
|
7
8
|
from openai.types.chat import ChatCompletionMessageParam
|
|
@@ -28,12 +29,16 @@ from mito_ai.completions.models import (
|
|
|
28
29
|
from mito_ai.utils.telemetry_utils import (
|
|
29
30
|
KEY_TYPE_PARAM,
|
|
30
31
|
MITO_AI_COMPLETION_ERROR,
|
|
32
|
+
MITO_AI_COMPLETION_RETRY,
|
|
31
33
|
MITO_SERVER_KEY,
|
|
32
34
|
USER_KEY,
|
|
33
35
|
log,
|
|
36
|
+
log_ai_completion_error,
|
|
37
|
+
log_ai_completion_retry,
|
|
34
38
|
log_ai_completion_success,
|
|
35
39
|
)
|
|
36
|
-
from mito_ai.
|
|
40
|
+
from mito_ai.utils.provider_utils import get_model_provider
|
|
41
|
+
from mito_ai.utils.mito_server_utils import ProviderCompletionException
|
|
37
42
|
|
|
38
43
|
__all__ = ["OpenAIProvider"]
|
|
39
44
|
|
|
@@ -66,6 +71,9 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
66
71
|
|
|
67
72
|
@property
|
|
68
73
|
def capabilities(self) -> AICapabilities:
|
|
74
|
+
"""
|
|
75
|
+
Returns the capabilities of the AI provider.
|
|
76
|
+
"""
|
|
69
77
|
if constants.CLAUDE_API_KEY and not self.api_key:
|
|
70
78
|
return AICapabilities(
|
|
71
79
|
configuration={"model": "<dynamic>"},
|
|
@@ -78,6 +86,7 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
78
86
|
)
|
|
79
87
|
if self._openai_client:
|
|
80
88
|
return self._openai_client.capabilities
|
|
89
|
+
|
|
81
90
|
return AICapabilities(
|
|
82
91
|
configuration={"model": "<dynamic>"},
|
|
83
92
|
provider="Mito server",
|
|
@@ -100,7 +109,8 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
100
109
|
model: str,
|
|
101
110
|
response_format_info: Optional[ResponseFormatInfo] = None,
|
|
102
111
|
user_input: Optional[str] = None,
|
|
103
|
-
thread_id: Optional[str] = None
|
|
112
|
+
thread_id: Optional[str] = None,
|
|
113
|
+
max_retries: int = 3
|
|
104
114
|
) -> str:
|
|
105
115
|
"""
|
|
106
116
|
Request completions from the AI provider.
|
|
@@ -109,43 +119,69 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
109
119
|
completion = None
|
|
110
120
|
last_message_content = str(messages[-1].get('content', '')) if messages else ""
|
|
111
121
|
model_type = get_model_provider(model)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
|
|
123
|
+
# Retry loop
|
|
124
|
+
for attempt in range(max_retries + 1):
|
|
125
|
+
try:
|
|
126
|
+
if model_type == "claude":
|
|
127
|
+
api_key = constants.CLAUDE_API_KEY
|
|
128
|
+
anthropic_client = AnthropicClient(api_key=api_key)
|
|
129
|
+
completion = await anthropic_client.request_completions(messages, model, response_format_info, message_type)
|
|
130
|
+
elif model_type == "gemini":
|
|
131
|
+
api_key = constants.GEMINI_API_KEY
|
|
132
|
+
gemini_client = GeminiClient(api_key=api_key)
|
|
133
|
+
messages_for_gemini = [dict(m) for m in messages]
|
|
134
|
+
completion = await gemini_client.request_completions(messages_for_gemini, model, response_format_info, message_type)
|
|
135
|
+
elif model_type == "openai":
|
|
136
|
+
if not self._openai_client:
|
|
137
|
+
raise RuntimeError("OpenAI client is not initialized.")
|
|
138
|
+
completion = await self._openai_client.request_completions(
|
|
139
|
+
message_type=message_type,
|
|
140
|
+
messages=messages,
|
|
141
|
+
model=model,
|
|
142
|
+
response_format_info=response_format_info
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"No AI provider configured for model: {model}")
|
|
146
|
+
|
|
147
|
+
# Success! Log and return
|
|
148
|
+
log_ai_completion_success(
|
|
149
|
+
key_type=USER_KEY if self.key_type == "user" else MITO_SERVER_KEY,
|
|
126
150
|
message_type=message_type,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
last_message_content=last_message_content,
|
|
152
|
+
response={"completion": completion},
|
|
153
|
+
user_input=user_input or "",
|
|
154
|
+
thread_id=thread_id or "",
|
|
155
|
+
model=model
|
|
130
156
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
thread_id=thread_id or "",
|
|
140
|
-
model=model
|
|
141
|
-
)
|
|
142
|
-
return completion
|
|
157
|
+
return completion # type: ignore
|
|
158
|
+
|
|
159
|
+
except PermissionError as e:
|
|
160
|
+
# If we hit a free tier limit, then raise an exception right away without retrying.
|
|
161
|
+
self.log.exception(f"Error during request_completions: {e}")
|
|
162
|
+
self.last_error = CompletionError.from_exception(e)
|
|
163
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
164
|
+
raise
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
except BaseException as e:
|
|
167
|
+
# Check if we should retry (not on the last attempt)
|
|
168
|
+
if attempt < max_retries:
|
|
169
|
+
# Exponential backoff: wait 2^attempt seconds
|
|
170
|
+
wait_time = 2 ** attempt
|
|
171
|
+
self.log.info(f"Retrying request_completions after {wait_time}s (attempt {attempt + 1}/{max_retries + 1}): {str(e)}")
|
|
172
|
+
log_ai_completion_retry('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
173
|
+
await asyncio.sleep(wait_time)
|
|
174
|
+
continue
|
|
175
|
+
else:
|
|
176
|
+
# Final failure after all retries - set error state and raise
|
|
177
|
+
self.log.exception(f"Error during request_completions after {attempt + 1} attempts: {e}")
|
|
178
|
+
self.last_error = CompletionError.from_exception(e)
|
|
179
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
# This should never be reached due to the raise in the except block,
|
|
183
|
+
# but added to satisfy the linter
|
|
184
|
+
raise RuntimeError("Unexpected code path in request_completions")
|
|
149
185
|
|
|
150
186
|
async def stream_completions(
|
|
151
187
|
self,
|
|
@@ -176,19 +212,23 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
176
212
|
try:
|
|
177
213
|
if model_type == "claude":
|
|
178
214
|
api_key = constants.CLAUDE_API_KEY
|
|
179
|
-
anthropic_client = AnthropicClient(api_key=api_key
|
|
180
|
-
accumulated_response = await anthropic_client.
|
|
215
|
+
anthropic_client = AnthropicClient(api_key=api_key)
|
|
216
|
+
accumulated_response = await anthropic_client.stream_completions(
|
|
181
217
|
messages=messages,
|
|
218
|
+
model=model,
|
|
182
219
|
message_type=message_type,
|
|
183
220
|
message_id=message_id,
|
|
184
221
|
reply_fn=reply_fn
|
|
185
222
|
)
|
|
186
223
|
elif model_type == "gemini":
|
|
187
224
|
api_key = constants.GEMINI_API_KEY
|
|
188
|
-
gemini_client = GeminiClient(api_key=api_key
|
|
225
|
+
gemini_client = GeminiClient(api_key=api_key)
|
|
226
|
+
# TODO: We shouldn't need to do this because the messages should already be dictionaries...
|
|
227
|
+
# but if we do have to do some pre-processing, we should do it in the gemini_client instead.
|
|
189
228
|
messages_for_gemini = [dict(m) for m in messages]
|
|
190
229
|
accumulated_response = await gemini_client.stream_completions(
|
|
191
230
|
messages=messages_for_gemini,
|
|
231
|
+
model=model,
|
|
192
232
|
message_id=message_id,
|
|
193
233
|
reply_fn=reply_fn,
|
|
194
234
|
message_type=message_type
|
mito_ai/constants.py
CHANGED
|
@@ -23,30 +23,9 @@ AZURE_OPENAI_API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION")
|
|
|
23
23
|
AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT")
|
|
24
24
|
AZURE_OPENAI_MODEL = os.environ.get("AZURE_OPENAI_MODEL")
|
|
25
25
|
|
|
26
|
-
def get_model_provider(model: str) -> Union[str, None]:
|
|
27
|
-
"""
|
|
28
|
-
Determine the model type based on the model name prefix
|
|
29
|
-
"""
|
|
30
|
-
if not model:
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
model_lower = model.lower()
|
|
34
|
-
|
|
35
|
-
if model_lower.startswith('claude'):
|
|
36
|
-
return 'claude'
|
|
37
|
-
elif model_lower.startswith('gemini'):
|
|
38
|
-
return 'gemini'
|
|
39
|
-
elif model_lower.startswith('ollama'):
|
|
40
|
-
return 'ollama'
|
|
41
|
-
elif model_lower.startswith('gpt'):
|
|
42
|
-
return 'openai'
|
|
43
|
-
|
|
44
|
-
return None
|
|
45
|
-
|
|
46
|
-
|
|
47
26
|
# Mito AI Base URLs and Endpoint Paths
|
|
48
|
-
MITO_PROD_BASE_URL = "https://
|
|
49
|
-
MITO_DEV_BASE_URL = "https://
|
|
27
|
+
MITO_PROD_BASE_URL = "https://7eax4i53f5odkshhlry4gw23by0yvnuv.lambda-url.us-east-1.on.aws/v1"
|
|
28
|
+
MITO_DEV_BASE_URL = "https://g5vwmogjg7gh7aktqezyrvcq6a0hyfnr.lambda-url.us-east-1.on.aws/v1"
|
|
50
29
|
|
|
51
30
|
# Set ACTIVE_BASE_URL manually
|
|
52
31
|
ACTIVE_BASE_URL = MITO_PROD_BASE_URL # Change to MITO_DEV_BASE_URL for dev
|
|
@@ -59,4 +38,12 @@ OPENAI_PATH = "openai/completions"
|
|
|
59
38
|
# Full URLs (always use ACTIVE_BASE_URL)
|
|
60
39
|
MITO_ANTHROPIC_URL = f"{ACTIVE_BASE_URL}/{ANTHROPIC_PATH}"
|
|
61
40
|
MITO_GEMINI_URL = f"{ACTIVE_BASE_URL}/{GEMINI_PATH}"
|
|
62
|
-
MITO_OPENAI_URL = f"{ACTIVE_BASE_URL}/{OPENAI_PATH}"
|
|
41
|
+
MITO_OPENAI_URL = f"{ACTIVE_BASE_URL}/{OPENAI_PATH}"
|
|
42
|
+
|
|
43
|
+
# Streamlit conversion endpoints
|
|
44
|
+
MITO_STREAMLIT_DEV_BASE_URL = "https://fr12uvtfy5.execute-api.us-east-1.amazonaws.com"
|
|
45
|
+
MITO_STREAMLIT_TEST_BASE_URL = "https://iyual08t6d.execute-api.us-east-1.amazonaws.com"
|
|
46
|
+
|
|
47
|
+
# Set ACTIVE_BASE_URL manually
|
|
48
|
+
# TODO: Modify to PROD url before release
|
|
49
|
+
ACTIVE_STREAMLIT_BASE_URL = MITO_STREAMLIT_TEST_BASE_URL # Change to MITO_STREAMLIT_DEV_BASE_URL for dev
|