mito-ai 0.1.50__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.
Files changed (205) hide show
  1. mito_ai/__init__.py +114 -0
  2. mito_ai/_version.py +4 -0
  3. mito_ai/anthropic_client.py +334 -0
  4. mito_ai/app_deploy/__init__.py +6 -0
  5. mito_ai/app_deploy/app_deploy_utils.py +44 -0
  6. mito_ai/app_deploy/handlers.py +345 -0
  7. mito_ai/app_deploy/models.py +98 -0
  8. mito_ai/app_manager/__init__.py +4 -0
  9. mito_ai/app_manager/handlers.py +167 -0
  10. mito_ai/app_manager/models.py +71 -0
  11. mito_ai/app_manager/utils.py +24 -0
  12. mito_ai/auth/README.md +18 -0
  13. mito_ai/auth/__init__.py +6 -0
  14. mito_ai/auth/handlers.py +96 -0
  15. mito_ai/auth/urls.py +13 -0
  16. mito_ai/chat_history/handlers.py +63 -0
  17. mito_ai/chat_history/urls.py +32 -0
  18. mito_ai/completions/completion_handlers/__init__.py +3 -0
  19. mito_ai/completions/completion_handlers/agent_auto_error_fixup_handler.py +59 -0
  20. mito_ai/completions/completion_handlers/agent_execution_handler.py +66 -0
  21. mito_ai/completions/completion_handlers/chat_completion_handler.py +141 -0
  22. mito_ai/completions/completion_handlers/code_explain_handler.py +113 -0
  23. mito_ai/completions/completion_handlers/completion_handler.py +42 -0
  24. mito_ai/completions/completion_handlers/inline_completer_handler.py +48 -0
  25. mito_ai/completions/completion_handlers/smart_debug_handler.py +160 -0
  26. mito_ai/completions/completion_handlers/utils.py +147 -0
  27. mito_ai/completions/handlers.py +415 -0
  28. mito_ai/completions/message_history.py +401 -0
  29. mito_ai/completions/models.py +404 -0
  30. mito_ai/completions/prompt_builders/__init__.py +3 -0
  31. mito_ai/completions/prompt_builders/agent_execution_prompt.py +57 -0
  32. mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +160 -0
  33. mito_ai/completions/prompt_builders/agent_system_message.py +472 -0
  34. mito_ai/completions/prompt_builders/chat_name_prompt.py +15 -0
  35. mito_ai/completions/prompt_builders/chat_prompt.py +116 -0
  36. mito_ai/completions/prompt_builders/chat_system_message.py +92 -0
  37. mito_ai/completions/prompt_builders/explain_code_prompt.py +32 -0
  38. mito_ai/completions/prompt_builders/inline_completer_prompt.py +197 -0
  39. mito_ai/completions/prompt_builders/prompt_constants.py +170 -0
  40. mito_ai/completions/prompt_builders/smart_debug_prompt.py +199 -0
  41. mito_ai/completions/prompt_builders/utils.py +84 -0
  42. mito_ai/completions/providers.py +284 -0
  43. mito_ai/constants.py +63 -0
  44. mito_ai/db/__init__.py +3 -0
  45. mito_ai/db/crawlers/__init__.py +6 -0
  46. mito_ai/db/crawlers/base_crawler.py +61 -0
  47. mito_ai/db/crawlers/constants.py +43 -0
  48. mito_ai/db/crawlers/snowflake.py +71 -0
  49. mito_ai/db/handlers.py +168 -0
  50. mito_ai/db/models.py +31 -0
  51. mito_ai/db/urls.py +34 -0
  52. mito_ai/db/utils.py +185 -0
  53. mito_ai/docker/mssql/compose.yml +37 -0
  54. mito_ai/docker/mssql/init/setup.sql +21 -0
  55. mito_ai/docker/mysql/compose.yml +18 -0
  56. mito_ai/docker/mysql/init/setup.sql +13 -0
  57. mito_ai/docker/oracle/compose.yml +17 -0
  58. mito_ai/docker/oracle/init/setup.sql +20 -0
  59. mito_ai/docker/postgres/compose.yml +17 -0
  60. mito_ai/docker/postgres/init/setup.sql +13 -0
  61. mito_ai/enterprise/__init__.py +3 -0
  62. mito_ai/enterprise/utils.py +15 -0
  63. mito_ai/file_uploads/__init__.py +3 -0
  64. mito_ai/file_uploads/handlers.py +248 -0
  65. mito_ai/file_uploads/urls.py +21 -0
  66. mito_ai/gemini_client.py +232 -0
  67. mito_ai/log/handlers.py +38 -0
  68. mito_ai/log/urls.py +21 -0
  69. mito_ai/logger.py +37 -0
  70. mito_ai/openai_client.py +382 -0
  71. mito_ai/path_utils.py +70 -0
  72. mito_ai/rules/handlers.py +44 -0
  73. mito_ai/rules/urls.py +22 -0
  74. mito_ai/rules/utils.py +56 -0
  75. mito_ai/settings/handlers.py +41 -0
  76. mito_ai/settings/urls.py +20 -0
  77. mito_ai/settings/utils.py +42 -0
  78. mito_ai/streamlit_conversion/agent_utils.py +37 -0
  79. mito_ai/streamlit_conversion/prompts/prompt_constants.py +172 -0
  80. mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
  81. mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +46 -0
  82. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
  83. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +45 -0
  84. mito_ai/streamlit_conversion/prompts/streamlit_system_prompt.py +56 -0
  85. mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
  86. mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
  87. mito_ai/streamlit_conversion/streamlit_agent_handler.py +144 -0
  88. mito_ai/streamlit_conversion/streamlit_utils.py +85 -0
  89. mito_ai/streamlit_conversion/validate_streamlit_app.py +105 -0
  90. mito_ai/streamlit_preview/__init__.py +6 -0
  91. mito_ai/streamlit_preview/handlers.py +111 -0
  92. mito_ai/streamlit_preview/manager.py +152 -0
  93. mito_ai/streamlit_preview/urls.py +22 -0
  94. mito_ai/streamlit_preview/utils.py +29 -0
  95. mito_ai/tests/__init__.py +3 -0
  96. mito_ai/tests/chat_history/test_chat_history.py +211 -0
  97. mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
  98. mito_ai/tests/conftest.py +53 -0
  99. mito_ai/tests/create_agent_system_message_prompt_test.py +22 -0
  100. mito_ai/tests/data/prompt_lg.py +69 -0
  101. mito_ai/tests/data/prompt_sm.py +6 -0
  102. mito_ai/tests/data/prompt_xl.py +13 -0
  103. mito_ai/tests/data/stock_data.sqlite3 +0 -0
  104. mito_ai/tests/db/conftest.py +39 -0
  105. mito_ai/tests/db/connections_test.py +102 -0
  106. mito_ai/tests/db/mssql_test.py +29 -0
  107. mito_ai/tests/db/mysql_test.py +29 -0
  108. mito_ai/tests/db/oracle_test.py +29 -0
  109. mito_ai/tests/db/postgres_test.py +29 -0
  110. mito_ai/tests/db/schema_test.py +93 -0
  111. mito_ai/tests/db/sqlite_test.py +31 -0
  112. mito_ai/tests/db/test_db_constants.py +61 -0
  113. mito_ai/tests/deploy_app/test_app_deploy_utils.py +89 -0
  114. mito_ai/tests/file_uploads/__init__.py +2 -0
  115. mito_ai/tests/file_uploads/test_handlers.py +282 -0
  116. mito_ai/tests/message_history/test_generate_short_chat_name.py +120 -0
  117. mito_ai/tests/message_history/test_message_history_utils.py +469 -0
  118. mito_ai/tests/open_ai_utils_test.py +152 -0
  119. mito_ai/tests/performance_test.py +329 -0
  120. mito_ai/tests/providers/test_anthropic_client.py +447 -0
  121. mito_ai/tests/providers/test_azure.py +631 -0
  122. mito_ai/tests/providers/test_capabilities.py +120 -0
  123. mito_ai/tests/providers/test_gemini_client.py +195 -0
  124. mito_ai/tests/providers/test_mito_server_utils.py +448 -0
  125. mito_ai/tests/providers/test_model_resolution.py +130 -0
  126. mito_ai/tests/providers/test_openai_client.py +57 -0
  127. mito_ai/tests/providers/test_provider_completion_exception.py +66 -0
  128. mito_ai/tests/providers/test_provider_limits.py +42 -0
  129. mito_ai/tests/providers/test_providers.py +382 -0
  130. mito_ai/tests/providers/test_retry_logic.py +389 -0
  131. mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
  132. mito_ai/tests/providers/utils.py +85 -0
  133. mito_ai/tests/rules/conftest.py +26 -0
  134. mito_ai/tests/rules/rules_test.py +117 -0
  135. mito_ai/tests/server_limits_test.py +406 -0
  136. mito_ai/tests/settings/conftest.py +26 -0
  137. mito_ai/tests/settings/settings_test.py +70 -0
  138. mito_ai/tests/settings/test_settings_constants.py +9 -0
  139. mito_ai/tests/streamlit_conversion/__init__.py +3 -0
  140. mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
  141. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +246 -0
  142. mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +193 -0
  143. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +112 -0
  144. mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +118 -0
  145. mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +292 -0
  146. mito_ai/tests/test_constants.py +47 -0
  147. mito_ai/tests/test_telemetry.py +12 -0
  148. mito_ai/tests/user/__init__.py +2 -0
  149. mito_ai/tests/user/test_user.py +120 -0
  150. mito_ai/tests/utils/__init__.py +3 -0
  151. mito_ai/tests/utils/test_anthropic_utils.py +162 -0
  152. mito_ai/tests/utils/test_gemini_utils.py +98 -0
  153. mito_ai/tests/version_check_test.py +169 -0
  154. mito_ai/user/handlers.py +45 -0
  155. mito_ai/user/urls.py +21 -0
  156. mito_ai/utils/__init__.py +3 -0
  157. mito_ai/utils/anthropic_utils.py +168 -0
  158. mito_ai/utils/create.py +94 -0
  159. mito_ai/utils/db.py +74 -0
  160. mito_ai/utils/error_classes.py +42 -0
  161. mito_ai/utils/gemini_utils.py +133 -0
  162. mito_ai/utils/message_history_utils.py +87 -0
  163. mito_ai/utils/mito_server_utils.py +242 -0
  164. mito_ai/utils/open_ai_utils.py +200 -0
  165. mito_ai/utils/provider_utils.py +49 -0
  166. mito_ai/utils/schema.py +86 -0
  167. mito_ai/utils/server_limits.py +152 -0
  168. mito_ai/utils/telemetry_utils.py +480 -0
  169. mito_ai/utils/utils.py +89 -0
  170. mito_ai/utils/version_utils.py +94 -0
  171. mito_ai/utils/websocket_base.py +88 -0
  172. mito_ai/version_check.py +60 -0
  173. mito_ai-0.1.50.data/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +7 -0
  174. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/build_log.json +728 -0
  175. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/package.json +243 -0
  176. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +238 -0
  177. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +37 -0
  178. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +21602 -0
  179. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
  180. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
  181. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
  182. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.78d3ccb73e7ca1da3aae.js +619 -0
  183. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.78d3ccb73e7ca1da3aae.js.map +1 -0
  184. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style.js +4 -0
  185. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +712 -0
  186. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
  187. mito_ai-0.1.50.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
  188. mito_ai-0.1.50.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
  189. mito_ai-0.1.50.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
  190. mito_ai-0.1.50.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
  191. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +1021 -0
  192. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +1 -0
  193. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +59698 -0
  194. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
  195. mito_ai-0.1.50.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
  196. mito_ai-0.1.50.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
  197. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2792 -0
  198. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
  199. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +4859 -0
  200. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +1 -0
  201. mito_ai-0.1.50.dist-info/METADATA +221 -0
  202. mito_ai-0.1.50.dist-info/RECORD +205 -0
  203. mito_ai-0.1.50.dist-info/WHEEL +4 -0
  204. mito_ai-0.1.50.dist-info/entry_points.txt +2 -0
  205. mito_ai-0.1.50.dist-info/licenses/LICENSE +3 -0
@@ -0,0 +1,211 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import pytest
5
+ import requests
6
+ import time
7
+ from unittest.mock import patch, MagicMock
8
+ from mito_ai.tests.conftest import TOKEN
9
+ from mito_ai.completions.message_history import GlobalMessageHistory, ChatThread
10
+ from mito_ai.completions.models import ThreadID
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_chat_threads():
15
+ """Fixture that creates mock chat threads for testing"""
16
+ thread_id_1 = ThreadID("test-thread-1")
17
+ thread_id_2 = ThreadID("test-thread-2")
18
+
19
+ # Create mock threads with different timestamps
20
+ thread_1 = ChatThread(
21
+ thread_id=thread_id_1,
22
+ creation_ts=time.time() - 3600, # 1 hour ago
23
+ last_interaction_ts=time.time() - 1800, # 30 minutes ago
24
+ name="Test Chat 1",
25
+ ai_optimized_history=[
26
+ {"role": "user", "content": "Hello"},
27
+ {"role": "assistant", "content": "Hi there!"},
28
+ ],
29
+ display_history=[
30
+ {"role": "user", "content": "Hello"},
31
+ {"role": "assistant", "content": "Hi there!"},
32
+ ],
33
+ )
34
+
35
+ thread_2 = ChatThread(
36
+ thread_id=thread_id_2,
37
+ creation_ts=time.time() - 7200, # 2 hours ago
38
+ last_interaction_ts=time.time() - 900, # 15 minutes ago (more recent)
39
+ name="Test Chat 2",
40
+ ai_optimized_history=[
41
+ {"role": "user", "content": "How are you?"},
42
+ {"role": "assistant", "content": "I'm doing well, thanks!"},
43
+ ],
44
+ display_history=[
45
+ {"role": "user", "content": "How are you?"},
46
+ {"role": "assistant", "content": "I'm doing well, thanks!"},
47
+ ],
48
+ )
49
+
50
+ return {thread_id_1: thread_1, thread_id_2: thread_2}
51
+
52
+
53
+ @pytest.fixture
54
+ def mock_message_history(mock_chat_threads):
55
+ """Fixture that mocks the GlobalMessageHistory with test data"""
56
+ mock_history = MagicMock(spec=GlobalMessageHistory)
57
+ mock_history._chat_threads = mock_chat_threads
58
+
59
+ # Mock the get_threads method to return sorted threads
60
+ def mock_get_threads():
61
+ from mito_ai.completions.models import ChatThreadMetadata
62
+
63
+ threads = []
64
+ for thread in mock_chat_threads.values():
65
+ threads.append(
66
+ ChatThreadMetadata(
67
+ thread_id=thread.thread_id,
68
+ name=thread.name,
69
+ creation_ts=thread.creation_ts,
70
+ last_interaction_ts=thread.last_interaction_ts,
71
+ )
72
+ )
73
+ # Sort by last_interaction_ts (newest first)
74
+ threads.sort(key=lambda x: x.last_interaction_ts, reverse=True)
75
+ return threads
76
+
77
+ mock_history.get_threads = mock_get_threads
78
+ return mock_history
79
+
80
+
81
+ # --- GET ALL THREADS ---
82
+
83
+
84
+ def test_get_all_threads_success(jp_base_url: str, mock_message_history):
85
+ """Test successful GET all threads endpoint"""
86
+ # Since the server extension is already loaded, we need to work with the actual instance
87
+ # Let's just test that the endpoint works and returns the expected structure
88
+ response = requests.get(
89
+ jp_base_url + "/mito-ai/chat-history/threads",
90
+ headers={"Authorization": f"token {TOKEN}"},
91
+ )
92
+ assert response.status_code == 200
93
+
94
+ response_json = response.json()
95
+ assert "threads" in response_json
96
+ # The actual number of threads will depend on what's in the .mito/ai-chats directory
97
+ # So we'll just check that it's a list
98
+ assert isinstance(response_json["threads"], list)
99
+
100
+ # Check thread structure for any threads that exist
101
+ for thread in response_json["threads"]:
102
+ assert "thread_id" in thread
103
+ assert "name" in thread
104
+ assert "creation_ts" in thread
105
+ assert "last_interaction_ts" in thread
106
+
107
+
108
+ def test_get_all_threads_empty(jp_base_url: str):
109
+ """Test GET all threads endpoint when no threads exist"""
110
+ # This test will work with whatever threads exist in the actual .mito/ai-chats directory
111
+ # We'll just verify the endpoint works and returns the expected structure
112
+ response = requests.get(
113
+ jp_base_url + "/mito-ai/chat-history/threads",
114
+ headers={"Authorization": f"token {TOKEN}"},
115
+ )
116
+ assert response.status_code == 200
117
+
118
+ response_json = response.json()
119
+ assert "threads" in response_json
120
+ assert isinstance(response_json["threads"], list)
121
+
122
+
123
+ def test_get_all_threads_with_no_auth(jp_base_url: str):
124
+ """Test GET all threads endpoint without authentication"""
125
+ response = requests.get(
126
+ jp_base_url + "/mito-ai/chat-history/threads",
127
+ )
128
+ assert response.status_code == 403 # Forbidden
129
+
130
+
131
+ def test_get_all_threads_with_incorrect_auth(jp_base_url: str):
132
+ """Test GET all threads endpoint with incorrect authentication"""
133
+ response = requests.get(
134
+ jp_base_url + "/mito-ai/chat-history/threads",
135
+ headers={"Authorization": f"token incorrect-token"},
136
+ )
137
+ assert response.status_code == 403 # Forbidden
138
+
139
+
140
+ # --- GET SPECIFIC THREAD ---
141
+
142
+
143
+ def test_get_specific_thread_success(jp_base_url: str, mock_message_history):
144
+ """Test successful GET specific thread endpoint"""
145
+ # First, get all threads to see what's available
146
+ response = requests.get(
147
+ jp_base_url + "/mito-ai/chat-history/threads",
148
+ headers={"Authorization": f"token {TOKEN}"},
149
+ )
150
+ assert response.status_code == 200
151
+
152
+ threads = response.json()["threads"]
153
+ if not threads:
154
+ # If no threads exist, skip this test
155
+ pytest.skip("No threads available for testing")
156
+
157
+ # Use the first available thread
158
+ thread_id = threads[0]["thread_id"]
159
+
160
+ response = requests.get(
161
+ jp_base_url + f"/mito-ai/chat-history/threads/{thread_id}",
162
+ headers={"Authorization": f"token {TOKEN}"},
163
+ )
164
+ assert response.status_code == 200
165
+
166
+ response_json = response.json()
167
+ assert response_json["thread_id"] == thread_id
168
+ assert "name" in response_json
169
+ assert "creation_ts" in response_json
170
+ assert "last_interaction_ts" in response_json
171
+ assert "display_history" in response_json
172
+ assert "ai_optimized_history" in response_json
173
+
174
+ # Check message history structure
175
+ display_history = response_json["display_history"]
176
+ assert isinstance(display_history, list)
177
+ ai_optimized_history = response_json["ai_optimized_history"]
178
+ assert isinstance(ai_optimized_history, list)
179
+
180
+
181
+ def test_get_specific_thread_not_found(jp_base_url: str, mock_message_history):
182
+ """Test GET specific thread endpoint with non-existent thread ID"""
183
+ # Use a clearly non-existent thread ID
184
+ fake_thread_id = "non-existent-thread-12345"
185
+
186
+ response = requests.get(
187
+ jp_base_url + f"/mito-ai/chat-history/threads/{fake_thread_id}",
188
+ headers={"Authorization": f"token {TOKEN}"},
189
+ )
190
+ assert response.status_code == 404
191
+
192
+ response_json = response.json()
193
+ assert "error" in response_json
194
+ assert fake_thread_id in response_json["error"]
195
+
196
+
197
+ def test_get_specific_thread_with_no_auth(jp_base_url: str):
198
+ """Test GET specific thread endpoint without authentication"""
199
+ response = requests.get(
200
+ jp_base_url + "/mito-ai/chat-history/threads/test-thread-1",
201
+ )
202
+ assert response.status_code == 403 # Forbidden
203
+
204
+
205
+ def test_get_specific_thread_with_incorrect_auth(jp_base_url: str):
206
+ """Test GET specific thread endpoint with incorrect authentication"""
207
+ response = requests.get(
208
+ jp_base_url + "/mito-ai/chat-history/threads/test-thread-1",
209
+ headers={"Authorization": f"token incorrect-token"},
210
+ )
211
+ assert response.status_code == 403 # Forbidden
@@ -0,0 +1,190 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import base64
5
+ import os
6
+ import tempfile
7
+ from contextlib import contextmanager
8
+ from mito_ai.completions.completion_handlers.utils import (
9
+ create_ai_optimized_message,
10
+ extract_and_encode_images_from_additional_context,
11
+ )
12
+
13
+
14
+ @contextmanager
15
+ def temporary_image_file(suffix=".png", content=b"fake_image_data"):
16
+ """Context manager that creates a temporary image file for testing."""
17
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
18
+ temp_file.write(content)
19
+ temp_file_path = temp_file.name
20
+
21
+ try:
22
+ yield temp_file_path
23
+ finally:
24
+ # Clean up the temporary file
25
+ os.unlink(temp_file_path)
26
+
27
+
28
+ def test_text_only_message():
29
+ """Test scenario where the user only inputs text"""
30
+ result = create_ai_optimized_message("Hello world")
31
+
32
+ assert result["role"] == "user"
33
+ assert result["content"] == "Hello world"
34
+
35
+
36
+ def test_message_with_uploaded_image():
37
+ """Test scenario where the user uploads an image"""
38
+ with temporary_image_file() as temp_file_path:
39
+ result = create_ai_optimized_message(
40
+ text="Analyze this",
41
+ additional_context=[{"type": "image/png", "value": temp_file_path}],
42
+ )
43
+
44
+ assert result["role"] == "user"
45
+ assert isinstance(result["content"], list)
46
+ assert result["content"][0]["type"] == "text"
47
+ assert result["content"][1]["type"] == "image_url"
48
+
49
+
50
+ def test_message_with_multiple_uploaded_images():
51
+ """Test scenario where the user uploads multiple images"""
52
+ with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
53
+ with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
54
+ result = create_ai_optimized_message(
55
+ text="Analyze these images",
56
+ additional_context=[
57
+ {"type": "image/png", "value": temp_file1},
58
+ {"type": "image/jpeg", "value": temp_file2},
59
+ ],
60
+ )
61
+
62
+ assert result["role"] == "user"
63
+ assert isinstance(result["content"], list)
64
+ assert len(result["content"]) == 3 # text + 2 images
65
+ assert result["content"][0]["type"] == "text"
66
+ assert result["content"][0]["text"] == "Analyze these images"
67
+ assert result["content"][1]["type"] == "image_url"
68
+ assert result["content"][2]["type"] == "image_url"
69
+
70
+ # Verify the image URLs are properly formatted
71
+ assert result["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
72
+ assert result["content"][2]["image_url"]["url"].startswith("data:image/jpeg;base64,")
73
+
74
+
75
+ def test_message_with_active_cell_output():
76
+ """Test scenario where the active cell has an output"""
77
+ result = create_ai_optimized_message(
78
+ text="Analyze this", base64EncodedActiveCellOutput="cell_output_data"
79
+ )
80
+
81
+ assert result["role"] == "user"
82
+ assert isinstance(result["content"], list)
83
+ assert result["content"][0]["type"] == "text"
84
+ assert result["content"][1]["type"] == "image_url"
85
+
86
+
87
+ def test_message_with_uploaded_image_and_active_cell_output():
88
+ """Test scenario where the user uploads an image and the active cell has an output"""
89
+ with temporary_image_file() as temp_file_path:
90
+ result = create_ai_optimized_message(
91
+ text="Analyze this",
92
+ additional_context=[{"type": "image/png", "value": temp_file_path}],
93
+ base64EncodedActiveCellOutput="cell_output_data",
94
+ )
95
+
96
+ assert result["role"] == "user"
97
+ assert isinstance(result["content"], list)
98
+ assert result["content"][0]["type"] == "text"
99
+ assert result["content"][1]["type"] == "image_url"
100
+ assert result["content"][2]["type"] == "image_url"
101
+
102
+
103
+ def test_extract_and_encode_images_from_additional_context_valid_image():
104
+ """Test extracting and encoding a valid image file"""
105
+ with temporary_image_file() as temp_file_path:
106
+ additional_context = [{"type": "image/png", "value": temp_file_path}]
107
+
108
+ encoded_images = extract_and_encode_images_from_additional_context(
109
+ additional_context
110
+ )
111
+
112
+ assert len(encoded_images) == 1
113
+ assert encoded_images[0].startswith("data:image/png;base64,")
114
+ # Verify it's valid base64 by checking it can be decoded
115
+ base64_data = encoded_images[0].split(",")[1]
116
+ decoded_data = base64.b64decode(base64_data)
117
+ assert decoded_data == b"fake_image_data"
118
+
119
+
120
+ def test_extract_and_encode_images_from_additional_context_multiple_images():
121
+ """Test extracting and encoding multiple image files"""
122
+ with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
123
+ with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
124
+ additional_context = [
125
+ {"type": "image/png", "value": temp_file1},
126
+ {"type": "image/jpeg", "value": temp_file2},
127
+ ]
128
+
129
+ encoded_images = extract_and_encode_images_from_additional_context(
130
+ additional_context
131
+ )
132
+
133
+ assert len(encoded_images) == 2
134
+ assert encoded_images[0].startswith("data:image/png;base64,")
135
+ assert encoded_images[1].startswith("data:image/jpeg;base64,")
136
+
137
+
138
+ def test_extract_and_encode_images_from_additional_context_invalid_file():
139
+ """Test handling of invalid/non-existent image files"""
140
+ additional_context = [{"type": "image/png", "value": "non_existent_file.png"}]
141
+
142
+ encoded_images = extract_and_encode_images_from_additional_context(
143
+ additional_context
144
+ )
145
+
146
+ assert len(encoded_images) == 0
147
+
148
+
149
+ def test_extract_and_encode_images_from_additional_context_non_image_types():
150
+ """Test that non-image types are ignored"""
151
+ with temporary_image_file(suffix=".txt", content=b"text_data") as temp_file:
152
+ additional_context = [
153
+ {"type": "text/plain", "value": temp_file},
154
+ {"type": "application/pdf", "value": "document.pdf"},
155
+ ]
156
+
157
+ encoded_images = extract_and_encode_images_from_additional_context(
158
+ additional_context
159
+ )
160
+
161
+ assert len(encoded_images) == 0
162
+
163
+
164
+ def test_extract_and_encode_images_from_additional_context_mixed_types():
165
+ """Test handling of mixed image and non-image types"""
166
+ with temporary_image_file() as temp_image_file:
167
+ additional_context = [
168
+ {"type": "image/png", "value": temp_image_file},
169
+ {"type": "text/plain", "value": "document.txt"},
170
+ {"type": "image/jpeg", "value": "non_existent.jpg"},
171
+ ]
172
+
173
+ encoded_images = extract_and_encode_images_from_additional_context(
174
+ additional_context
175
+ )
176
+
177
+ # Should only have the valid PNG image
178
+ assert len(encoded_images) == 1
179
+ assert encoded_images[0].startswith("data:image/png;base64,")
180
+
181
+
182
+ def test_extract_and_encode_images_from_additional_context_empty():
183
+ """Test handling of empty or None additional_context"""
184
+ # Test with None
185
+ encoded_images = extract_and_encode_images_from_additional_context(None)
186
+ assert len(encoded_images) == 0
187
+
188
+ # Test with empty list
189
+ encoded_images = extract_and_encode_images_from_additional_context([])
190
+ assert len(encoded_images) == 0
@@ -0,0 +1,53 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import pytest
5
+ import threading
6
+ import time
7
+ from jupyter_server.serverapp import ServerApp
8
+ from traitlets.config import Config
9
+
10
+ TOKEN = "test-token"
11
+
12
+ @pytest.fixture
13
+ def jp_server_config(token_fixture):
14
+ """Configure the Jupyter server for testing."""
15
+ config = Config()
16
+ config.ServerApp.jpserver_extensions = {"mito_ai": True}
17
+ # Disable password requirement for testing
18
+ config.ServerApp.password = ""
19
+ # Set the token for testing
20
+ config.ServerApp.token = token_fixture
21
+ # Enable authentication
22
+ config.ServerApp.allow_unauthenticated_access = False
23
+ return config
24
+
25
+ @pytest.fixture
26
+ def jp_serverapp(jp_server_config, tmp_path):
27
+ """Create a Jupyter server instance for testing."""
28
+ app = ServerApp(config=jp_server_config)
29
+ app.root_dir = str(tmp_path)
30
+ app.initialize(argv=[])
31
+
32
+ # Start the server in a non-blocking way
33
+ def start_server():
34
+ app.start()
35
+
36
+ server_thread = threading.Thread(target=start_server)
37
+ server_thread.daemon = True # This ensures the thread will be killed when the main program exits
38
+ server_thread.start()
39
+
40
+ # Give the server a moment to start
41
+ time.sleep(1)
42
+
43
+ yield app
44
+ app.stop()
45
+
46
+ @pytest.fixture
47
+ def jp_base_url(jp_serverapp):
48
+ """Get the base URL of the Jupyter server."""
49
+ return jp_serverapp.connection_url
50
+
51
+ @pytest.fixture
52
+ def token_fixture():
53
+ return TOKEN
@@ -0,0 +1,22 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import pytest
5
+ from mito_ai.completions.prompt_builders.agent_system_message import create_agent_system_message_prompt
6
+
7
+ def test_create_agent_system_message_prompt_browser_conditional() -> None:
8
+ """
9
+ Test that verifies the create_agent_system_message_prompt function produces different output
10
+ based on the browser type.
11
+
12
+ Specifically:
13
+ - When isChromeBrowser=False, the prompt should NOT contain GET_CELL_OUTPUT
14
+ - When isChromeBrowser=True, the prompt SHOULD contain GET_CELL_OUTPUT
15
+ """
16
+ # Test non-Chrome browser case
17
+ non_chrome_prompt = create_agent_system_message_prompt(isChromeBrowser=False)
18
+ assert "GET_CELL_OUTPUT" not in non_chrome_prompt, "Non-Chrome prompt should not contain GET_CELL_OUTPUT"
19
+
20
+ # Test Chrome browser case
21
+ chrome_prompt = create_agent_system_message_prompt(isChromeBrowser=True)
22
+ assert "GET_CELL_OUTPUT" in chrome_prompt, "Chrome prompt should contain GET_CELL_OUTPUT"
@@ -0,0 +1,69 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ prompt_lg = [
5
+ {
6
+ "role": "user",
7
+ "content": """You are an expert python programmer writing a script in a Jupyter notebook. You are given a set of variables, existing code, and a task.
8
+
9
+ There are two possible types of responses you might give:
10
+ 1. Code Update: If the task requires modifying or extending the existing code, respond with the updated active code cell and a short explanation of the changes made.
11
+ 2. Explanation/Information: If the task does not require a code update, provide an explanation, additional information about a package, method, or general programming question, without writing any code. Keep your response concise and to the point.
12
+
13
+ When responding:
14
+ - Do not use the word "I"
15
+ - Do not recreate variables that already exist
16
+ - Keep as much of the original code as possible
17
+
18
+ <Example>
19
+
20
+ Files in the current directory:
21
+ file_name: sales.csv
22
+
23
+ Defined Variables:
24
+ {{
25
+ 'loan_multiplier': 1.5,
26
+ 'sales_df': pd.DataFrame({{
27
+ 'transaction_date': ['2024-01-02', '2024-01-02', '2024-01-02', '2024-01-02', '2024-01-03'],
28
+ 'price_per_unit': [10, 9.99, 13.99, 21.00, 100],
29
+ 'units_sold': [1, 2, 1, 4, 5],
30
+ 'total_price': [10, 19.98, 13.99, 84.00, 500]
31
+ }})
32
+ }}
33
+
34
+ Code in the active code cell:
35
+ ```python
36
+ import pandas as pd
37
+ sales_df = pd.read_csv('./sales.csv')
38
+ ```
39
+
40
+ Your task: convert the transaction_date column to datetime and then multiply the total_price column by the sales_multiplier.
41
+
42
+ Output:
43
+ ```python
44
+ import pandas as pd
45
+ sales_df = pd.read_csv('./sales.csv')
46
+ sales_df['transaction_date'] = pd.to_datetime(sales_df['transaction_date'])
47
+ sales_df['total_price'] = sales_df['total_price'] * sales_multiplier
48
+ ```
49
+
50
+ Converted the `transaction_date` column to datetime using the built-in pd.to_datetime function and multiplied the `total_price` column by the `sales_multiplier` variable.
51
+
52
+ </Example>
53
+
54
+ Files in the current directory:
55
+
56
+ Defined Variables:
57
+
58
+ Code in the active code cell:
59
+ ```python
60
+ ```
61
+
62
+ Your task:
63
+ Create a dataset about the stock market. It should have fundamental data
64
+ going back 10 years for 100 companies. There should be some correlation in
65
+ the dataset, I don't care what columns. Once the dataset is created, create
66
+ three charts to help me understand the data. Please select the topic for
67
+ these charts based on details you think are pertinent.""",
68
+ },
69
+ ]
@@ -0,0 +1,6 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ prompt_sm = """
5
+ Can you create a function that perfoms fizz buzz. It should take an integer n and return a list of strings.
6
+ """
@@ -0,0 +1,13 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ prompt_xl = [
5
+ {
6
+ "role": "system",
7
+ "content": "You are Mito Data Copilot, an AI assistant for Jupyter. You\u2019re a great python programmer, a seasoned data scientist and a subject matter expert.\n\nThe user is going to ask you to guide them as they complete a task. You will help them complete a task over the course of an entire conversation with them. The user will first share with you what they want to accomplish. You will then give them the first step of the task, they will apply that first step, share the updated notebook state with you, and then you will give them the next step of the task. You will continue to give them the next step of the task until they have completed the task.\n\nTo communicate with the user, you will send them a CELL_UPDATE or FINISHED_TASK response in each message. Each time you respond with a CELL_UPDATE, the user will apply the CELL_UPDATE to the notebook, run the new code cell, and provide you with updated information about the notebook and variables defined in the kernel to help you decide what to do next.\n\n====\n\nCELL_UPDATES\n\nCELL_UPDATES are how you communicate to the user about the changes you want to make to the notebook. Each CELL_UPDATE can either modify an existing cell or create a new cell. \n\nThere are two types of CELL_UPDATES:\n\n1. CellModification\n2. CellAddition\n\nEach time you want to make a change to the notebook, you will respond with a CellModification or CellAddition.\n\n#### Cell Modification\nWhen you want to modify an existing cell in the notebook, respond in this format.\n\nFormat:\n{{\n is_finished: false, \n cell_update: {{\n type: modification\n id: str,\n code: str\n }}\n}}\n\nImportant information:\n1. 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.\n2. 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.\n\n#### Cell Addition:\nWhen you want to add a new cell to the notebook, respond in this format\n\nFormat: \n{{\n is_finished: false, \n cell_update: {{\n type: 'new'\n index: int\n code: str \n }}\n}}\n\nImportant information:\n1. The index should be the 0-index position of where you want the new code cell to be added in the notebook.\n2. 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.\n\n<Cell Modification Example>\nJupyter Notebook:\n[\n {{\n cell_type: 'markdown'\n id: '9e38c62b-38f8-457d-bb8d-28bfc52edf2c'\n code: \"\"\" # Used Car Sales Analysis \"\"\"\n }},\n {{\n cell_type: 'code'\n id: 'c68fdf19-db8c-46dd-926f-d90ad35bb3bc'\n code: \"\"\"import pandas as pd\nsales_df = pd.read_csv('./sales.csv') \nloan_multiplier = 1.5\"\"\"\n }},\n]\n\nDefined Variables:\n{{\n 'loan_multiplier': 1.5,\n 'sales_df': pd.DataFrame({{\n 'transaction_date': ['2024-01-02', '2024-01-02', '2024-01-02', '2024-01-02', '2024-01-03'],\n 'price_per_unit': [10, 9.99, 13.99, 21.00, 100],\n 'units_sold': [1, 2, 1, 4, 5],\n 'total_price': [10, 19.98, 13.99, 84.00, 500]\n }})\n}}\n\nFiles in the current directory:\nfile_name: sales.csv\n\nYour task: \nConvert the transaction_date column to datetime and then multiply the total_price column by the sales_multiplier.\n\nOutput:\n{{\n is_finished: false, \n cell_update: {{\n type: 'modification'\n id: 'c68fdf19-db8c-46dd-926f-d90ad35bb3bc',\n 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\"\n }}\n}}\n\n</Cell Modification Example>\n\n<Cell Addition Example>\nJupyter Notebook:\n[\n {{\n cell_type: 'markdown'\n id: '9e38c62b-38f8-457d-bb8d-28bfc52edf2c'\n code: \"\"\"# Used Car Sales Analysis \"\"\"\n }},\n {{\n cell_type: 'code'\n id: 'c68fdf19-db8c-46dd-926f-d90ad35bb3bc'\n code: \"\"\"import pandas as pd\nsales_df = pd.read_csv('./sales.csv')\nsales_df['transaction_date'] = pd.to_datetime(sales_df['transaction_date'])\"\"\"\n }},\n]\n\nDefined Variables:\n{{\n 'sales_df': pd.DataFrame({{\n 'transaction_date': ['2024-01-02', '2024-01-02', '2024-01-02', '2024-01-02', '2024-01-03'],\n 'price_per_unit': [10, 9.99, 13.99, 21.00, 100],\n 'units_sold': [1, 2, 1, 4, 5],\n 'total_price': [10, 19.98, 13.99, 84.00, 500]\n }})\n}}\n\nFiles in the current directory:\nfile_name: sales.csv\n\nYour task: \nGraph the total_price for each sale\n\nOutput:\n{{\n is_finished: false, \n cell_update: {{\n type: 'add'\n index: 2\n 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()\"\n }}\n}}\n\n</Cell Addition Example>\n\n====\n\nFINISHED_TASK\n\nWhen you have completed the user's task, respond with a message in this format:\n\n{{\n is_finished: true\n}}\n\n====\n\nRULES\n\n- You are working in a Jupyter Lab environment in a .ipynb file. \n- You can only respond with CELL_UPDATES or FINISHED_TASK responses.\n- In each message you send to the user, you can send one CellModification, one CellAddition, or one FINISHED_TASK response. BUT YOU WILL GET TO SEND MULTIPLE MESSAGES TO THE USER TO ACCOMPLISH YOUR TASK SO DO NOT TRY TO ACCOMPLISH YOUR TASK IN A SINGLE MESSAGE.\n- After you send a CELL_UPDATE, the user will send you a message with the updated variables, code, and files in the current directory. You will use this information to decide what to do next, so it is critical that you wait for the user's response after each CELL_UPDATE before deciding your next action.\n- When updating code, keep as much of the original code as possible and do not recreate variables that already exist.\n- When you want to display a dataframe to the user, just write the dataframe on the last line of the code cell instead of writing print(<dataframe name>). Jupyter will automatically display the dataframe in the notebook.\n\n====\n\nRULES OF YOUR WORKING PROCESS\n\n\nThe user is going to ask you to guide them as through the process of completing a task. You will help them complete a task over the course of an entire conversation with them. The user will first share with you what they want to accomplish. You will then give them the first step of the task, they will apply that first step, share the updated notebook state with you, and then you will give them the next step of the task. You will continue to give them the next step of the task until they have completed the task.\n\nAs you are guiding the user through the process of completing the task, send them a CELL_UPDATE message to give them the next step of the task. When you have finished the task, send a FINISHED_TASK message. \n\nThe user is a beginning Python user, so you will need to be careful to send them only small steps to complete. Don't try to complete the task in a single response to the user. Instead, each message you send to the user should only contain a single, small step towards the end goal. When the user has completed the step, they will let you know that they are ready for the next step. \n\nYou will keep working in the following iterative format until you have decided that you have finished the user's request. When you decide that you have finished the user's request, respond with a FINISHED_TASK message. Otherwise, if you have not finished the user's request, respond with a CELL_UPDATE. When you respond with a CELL_UPDATE, the user will apply the CELL_UPDATE to the notebook and run the new code cell. The user will then send you a message with an updated version of the variables defined in the kernel, code in the notebook, and files in the current directory. In addition, the user will check if the code you provided produced an errored when executed. If it did produce an error, the user will share the error message with you.\n\nWhenever you get a message back from the user, you should:\n1. Ask yourself if the previous CELL_UDPATE is correct. You can answer this question by reviewing the updated variable and code. \n2. Ask yourself if you can improve the code or results you got from the previous CELL_UPDATE. If you can, send a new CELL_UPDATE to modify the code you just wrote. \n3. Decide if you have finished the user's request to you. If you have, respond with a FINISHED_TASK message.\n4. If you have not finished the user's request, create the next CELL_UPDATE. \n\nREMEMBER, YOU ARE GOING TO COMPLETE THE USER'S TASK OVER THE COURSE OF THE ENTIRE CONVERSATION -- YOU WILL GET TO SEND MULTIPLE MESSAGES TO THE USER TO ACCOMPLISH YOUR TASK SO DO NOT TRY TO ACCOMPLISH YOUR TASK IN A SINGLE MESSAGE. IT IS CRUCIAL TO PROCEED STEP-BY-STEP WITH THE SMALLEST POSSIBLE CELL_UPDATES. For example, if asked to build a new dataframe, then analyze it, and then graph the results, you should proceed as follows. \n1. Send a CellAddition to add a new code cell to the notebook that creates the dataframe.\n2. Wait for the user to send you back the updated variables and notebook state so you can decide how to analyze the dataframe.\n3. Use the data that the user sent you to decide how to analyze the dataframe. Send a CellAddition to add the dataframe analysis code to the notebook.\n4. Wait for the user to send you back the updated variables and notebook state so you can decide how to proceed. \n5. If after reviewing the updates provided by the user, you decide that you want to update the analysis code, send a CellModification to modify the code you just wrote.\n6. Wait for the user to send you back the updated variables and notebook state so you can decide how to proceed.\n7. If you are happy with the analysis, refer back to the original task provided by the user to decide your next steps. In this example, it is to graph the results, so you will send a CellAddition to construct the graph. \n8. Wait for the user to send you back the updated variables and notebook state so you can decide how to proceed.\n9. If after reviewing the updates you decide that you've completed the task, send a FINISHED_TASK message.",
8
+ },
9
+ {
10
+ "role": "user",
11
+ "content": "Jupyter Notebook:\n{'id': '6c622de6-1eaa-48df-97c9-c6a52b095ac1', 'cell_type': 'code', 'code': \"import pandas as pd\\n\\n# Create a dataframe of NBA players with some sample data\\ndata = {\\n 'Name': ['LeBron James', 'Kevin Durant', 'Stephen Curry', 'Giannis Antetokounmpo', 'Luka Doncic', 'Nikola Jokic'],\\n 'Position': ['SF', 'PF', 'PG', 'PF', 'PG', 'C'],\\n 'Team': ['Lakers', 'Nets', 'Warriors', 'Bucks', 'Mavericks', 'Nuggets'],\\n 'Points': [27.0, 26.9, 28.1, 29.5, 27.7, 27.0],\\n 'Rebounds': [7.4, 7.1, 5.3, 13.6, 8.0, 10.8],\\n 'Assists': [7.4, 5.6, 6.3, 5.6, 8.6, 8.5]\\n}\\n\\nnba_players = pd.DataFrame(data)\\nnba_players \"}\n{'id': 'a173a08a-5afa-4449-bb4b-499ae8861b01', 'cell_type': 'code', 'code': \"## Build an All-Star Team\\n# We choose one best player per position based on the highest Points\\nall_star_team = nba_players.sort_values('Points', ascending=False).drop_duplicates('Position')\\n\\n# Display the All-Star team\\nall_star_team\\n\\n## Graphs for the All-Star Team\\nimport matplotlib.pyplot as plt\\n\\n# Graph 1: Points per All-Star Team member\\nplt.figure(figsize=(8, 4))\\nplt.bar(all_star_team['Name'], all_star_team['Points'], color='skyblue')\\nplt.title('All-Star Team: Points')\\nplt.xlabel('Player Name')\\nplt.ylabel('Points')\\nplt.xticks(rotation=45)\\nplt.show()\\n\\n# Graph 2: Rebounds per All-Star Team member\\nplt.figure(figsize=(8, 4))\\nplt.bar(all_star_team['Name'], all_star_team['Rebounds'], color='lightgreen')\\nplt.title('All-Star Team: Rebounds')\\nplt.xlabel('Player Name')\\nplt.ylabel('Rebounds')\\nplt.xticks(rotation=45)\\nplt.show()\\n\\n# Graph 3: Assists per All-Star Team member\\nplt.figure(figsize=(8, 4))\\nplt.bar(all_star_team['Name'], all_star_team['Assists'], color='salmon')\\nplt.title('All-Star Team: Assists')\\nplt.xlabel('Player Name')\\nplt.ylabel('Assists')\\nplt.xticks(rotation=45)\\nplt.show() \"}\n{'id': 'e25f1dd6-66b8-4769-97f8-bd956502ee4d', 'cell_type': 'code', 'code': '## Additional Graph: Scatter Plot of Points vs Rebounds (Bubble size indicates Assists)\\nimport matplotlib.pyplot as plt\\n\\nplt.figure(figsize=(8, 6))\\nplt.scatter(all_star_team[\\'Points\\'], all_star_team[\\'Rebounds\\'], s=all_star_team[\\'Assists\\']*20, color=\\'purple\\', alpha=0.6)\\n\\n# Annotate each point with the player\\'s name\\nfor idx, row in all_star_team.iterrows():\\n plt.annotate(row[\\'Name\\'], (row[\\'Points\\'], row[\\'Rebounds\\']), textcoords=\"offset points\", xytext=(5,5))\\n\\nplt.title(\\'All-Star Team: Points vs Rebounds (Bubble size by Assists)\\')\\nplt.xlabel(\\'Points\\')\\nplt.ylabel(\\'Rebounds\\')\\nplt.show()\\n '}\n{'id': 'e2447e90-6645-4f59-a31c-3d160292ed42', 'cell_type': 'code', 'code': ''}\n{'id': '41f6c119-e21d-4154-a299-211287bb3c37', 'cell_type': 'code', 'code': ''}\n{'id': 'd0c4c623-7052-40b2-b864-400260c2bdce', 'cell_type': 'code', 'code': ''}\n{'id': 'ff0ff671-e054-4ca4-b337-4b237a4f602b', 'cell_type': 'code', 'code': ''}\n\nDefined Variables:\n{'variable_name': 'pd', 'type': \"<class 'module'>\", 'value': \"<module 'pandas' from '/Users/aarondiamond-reivich/Mito/mito/mito-ai/venv/lib/python3.11/site-packages/pandas/__init__.py'>\"}\n{'variable_name': 'data', 'type': \"<class 'dict'>\", 'value': \"{'Name': ['LeBron James', 'Kevin Durant', 'Stephen Curry', 'Giannis Antetokounmpo', 'Luka Doncic', 'Nikola Jokic'], 'Position': ['SF', 'PF', 'PG', 'PF', 'PG', 'C'], 'Team': ['Lakers', 'Nets', 'Warriors', 'Bucks', 'Mavericks', 'Nuggets'], 'Points': [27.0, 26.9, 28.1, 29.5, 27.7, 27.0], 'Rebounds': [7.4, 7.1, 5.3, 13.6, 8.0, 10.8], 'Assists': [7.4, 5.6, 6.3, 5.6, 8.6, 8.5]}\"}\n{'variable_name': 'nba_players', 'type': 'pd.DataFrame', 'value': {'Name': {'dtype': 'object', 'samples': ['LeBron James', 'Kevin Durant', 'Stephen Curry', 'Giannis Antetokounmpo', 'Luka Doncic']}, 'Position': {'dtype': 'object', 'samples': ['SF', 'PF', 'PG', 'PF', 'PG']}, 'Team': {'dtype': 'object', 'samples': ['Lakers', 'Nets', 'Warriors', 'Bucks', 'Mavericks']}, 'Points': {'dtype': 'float64', 'samples': [27, 26.9, 28.1, 29.5, 27.7]}, 'Rebounds': {'dtype': 'float64', 'samples': [7.4, 7.1, 5.3, 13.6, 8]}, 'Assists': {'dtype': 'float64', 'samples': [7.4, 5.6, 6.3, 5.6, 8.6]}}}\n{'variable_name': 'all_star_team', 'type': 'pd.DataFrame', 'value': {'Name': {'dtype': 'object', 'samples': ['Giannis Antetokounmpo', 'Stephen Curry', 'LeBron James', 'Nikola Jokic']}, 'Position': {'dtype': 'object', 'samples': ['PF', 'PG', 'SF', 'C']}, 'Team': {'dtype': 'object', 'samples': ['Bucks', 'Warriors', 'Lakers', 'Nuggets']}, 'Points': {'dtype': 'float64', 'samples': [29.5, 28.1, 27, 27]}, 'Rebounds': {'dtype': 'float64', 'samples': [13.6, 5.3, 7.4, 10.8]}, 'Assists': {'dtype': 'float64', 'samples': [5.6, 6.3, 7.4, 8.5]}}}\n{'variable_name': 'plt', 'type': \"<class 'module'>\", 'value': \"<module 'matplotlib.pyplot' from '/Users/aarondiamond-reivich/Mito/mito/mito-ai/venv/lib/python3.11/site-packages/matplotlib/pyplot.py'>\"}\n{'variable_name': 'idx', 'type': \"<class 'int'>\", 'value': '5'}\n{'variable_name': 'row', 'type': \"<class 'pandas.core.series.Series'>\", 'value': 'Name Nikola Jokic\\nPosition C\\nTeam Nuggets\\nPoints 27.0\\nRebounds 10.8\\nAssists 8.5\\nName: 5, dtype: object'}\n\nFiles in the current directory:\n{'file_name': 'used_cars.csv'}\n\nYour task: \nUsing the analysis that you started, add another 20 players to the NBA dataset that you created and then use a more advanced strategy for figuring out who should be on the east and west coast all star team",
12
+ },
13
+ ]
Binary file
@@ -0,0 +1,39 @@
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 json
6
+ import pytest
7
+ import shutil
8
+ from typing import Generator
9
+ from mito_ai.tests.db.test_db_constants import (
10
+ DB_DIR_PATH,
11
+ CONNECTIONS_PATH,
12
+ BACKUP_DB_DIR_PATH,
13
+ )
14
+
15
+
16
+ @pytest.fixture(scope="module", autouse=True)
17
+ def backup_db_folder() -> Generator[None, None, None]:
18
+ """Backup the DB folder before tests and restore it after."""
19
+ if os.path.exists(DB_DIR_PATH):
20
+ # Create backup, so we can test with a clean db dir
21
+ shutil.move(DB_DIR_PATH, BACKUP_DB_DIR_PATH)
22
+
23
+ yield
24
+
25
+ # Cleanup after tests
26
+ if os.path.exists(DB_DIR_PATH):
27
+ shutil.rmtree(DB_DIR_PATH)
28
+ if os.path.exists(BACKUP_DB_DIR_PATH):
29
+ shutil.move(BACKUP_DB_DIR_PATH, DB_DIR_PATH)
30
+
31
+
32
+ @pytest.fixture
33
+ def first_connection_id() -> str:
34
+ # Manually open the connections.json file and get the first connection ID
35
+ with open(CONNECTIONS_PATH, "r") as f:
36
+ connections = json.load(f)
37
+ # Get the first connection ID from the object keys
38
+ connection_id = next(iter(connections.keys()))
39
+ return str(connection_id)