mito-ai 0.1.55__py3-none-any.whl → 0.1.56__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 (69) hide show
  1. mito_ai/_version.py +1 -1
  2. mito_ai/anthropic_client.py +7 -6
  3. mito_ai/completions/models.py +1 -1
  4. mito_ai/completions/prompt_builders/agent_execution_prompt.py +18 -50
  5. mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +77 -92
  6. mito_ai/completions/prompt_builders/agent_system_message.py +211 -275
  7. mito_ai/completions/prompt_builders/chat_prompt.py +15 -100
  8. mito_ai/completions/prompt_builders/chat_system_message.py +96 -72
  9. mito_ai/completions/prompt_builders/explain_code_prompt.py +22 -24
  10. mito_ai/completions/prompt_builders/inline_completer_prompt.py +78 -107
  11. mito_ai/completions/prompt_builders/prompt_constants.py +10 -48
  12. mito_ai/completions/prompt_builders/prompt_section_registry/__init__.py +70 -0
  13. mito_ai/completions/prompt_builders/prompt_section_registry/active_cell_code.py +15 -0
  14. mito_ai/completions/prompt_builders/prompt_section_registry/active_cell_id.py +10 -0
  15. mito_ai/completions/prompt_builders/prompt_section_registry/active_cell_output.py +20 -0
  16. mito_ai/completions/prompt_builders/prompt_section_registry/base.py +37 -0
  17. mito_ai/completions/prompt_builders/prompt_section_registry/error_traceback.py +17 -0
  18. mito_ai/completions/prompt_builders/prompt_section_registry/example.py +19 -0
  19. mito_ai/completions/prompt_builders/prompt_section_registry/files.py +17 -0
  20. mito_ai/completions/prompt_builders/prompt_section_registry/generic.py +15 -0
  21. mito_ai/completions/prompt_builders/prompt_section_registry/get_cell_output_tool_response.py +21 -0
  22. mito_ai/completions/prompt_builders/prompt_section_registry/notebook.py +19 -0
  23. mito_ai/completions/prompt_builders/prompt_section_registry/rules.py +39 -0
  24. mito_ai/completions/prompt_builders/{utils.py → prompt_section_registry/selected_context.py} +51 -42
  25. mito_ai/completions/prompt_builders/prompt_section_registry/streamlit_app_status.py +25 -0
  26. mito_ai/completions/prompt_builders/prompt_section_registry/task.py +12 -0
  27. mito_ai/completions/prompt_builders/prompt_section_registry/variables.py +18 -0
  28. mito_ai/completions/prompt_builders/smart_debug_prompt.py +48 -63
  29. mito_ai/constants.py +0 -3
  30. mito_ai/tests/completions/test_prompt_section_registry.py +44 -0
  31. mito_ai/tests/message_history/test_message_history_utils.py +273 -340
  32. mito_ai/tests/providers/test_anthropic_client.py +7 -3
  33. mito_ai/utils/message_history_utils.py +68 -44
  34. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
  35. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  36. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
  37. mito_ai-0.1.55.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.49c79c62671528877c61.js → mito_ai-0.1.56.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.dfd7975de75d64db80d6.js +487 -120
  38. mito_ai-0.1.56.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.dfd7975de75d64db80d6.js.map +1 -0
  39. mito_ai-0.1.55.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9dfbffc3592eb6f0aef9.js → mito_ai-0.1.56.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.1e7b5cf362385f109883.js +3 -3
  40. mito_ai-0.1.55.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9dfbffc3592eb6f0aef9.js.map → mito_ai-0.1.56.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.1e7b5cf362385f109883.js.map +1 -1
  41. {mito_ai-0.1.55.dist-info → mito_ai-0.1.56.dist-info}/METADATA +5 -1
  42. {mito_ai-0.1.55.dist-info → mito_ai-0.1.56.dist-info}/RECORD +68 -52
  43. mito_ai-0.1.55.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.49c79c62671528877c61.js.map +0 -1
  44. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  45. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
  46. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +0 -0
  47. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +0 -0
  48. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  49. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.f5d476ac514294615881.js +0 -0
  50. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.f5d476ac514294615881.js.map +0 -0
  51. {mito_ai-0.1.55.data → mito_ai-0.1.56.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 +0 -0
  52. {mito_ai-0.1.55.data → mito_ai-0.1.56.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 +0 -0
  53. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +0 -0
  54. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +0 -0
  55. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
  56. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
  57. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +0 -0
  58. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +0 -0
  59. {mito_ai-0.1.55.data → mito_ai-0.1.56.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 +0 -0
  60. {mito_ai-0.1.55.data → mito_ai-0.1.56.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 +0 -0
  61. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +0 -0
  62. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +0 -0
  63. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
  64. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
  65. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/themes/mito_ai/index.css +0 -0
  66. {mito_ai-0.1.55.data → mito_ai-0.1.56.data}/data/share/jupyter/labextensions/mito_ai/themes/mito_ai/index.js +0 -0
  67. {mito_ai-0.1.55.dist-info → mito_ai-0.1.56.dist-info}/WHEEL +0 -0
  68. {mito_ai-0.1.55.dist-info → mito_ai-0.1.56.dist-info}/entry_points.txt +0 -0
  69. {mito_ai-0.1.55.dist-info → mito_ai-0.1.56.dist-info}/licenses/LICENSE +0 -0
@@ -11,21 +11,7 @@ import json
11
11
  from typing import Final
12
12
  from mito_ai.utils.schema import MITO_FOLDER
13
13
 
14
- # Section headings used in prompts
15
- FILES_SECTION_HEADING = "Files in the current directory:"
16
- VARIABLES_SECTION_HEADING = "Defined Variables:"
17
- CODE_SECTION_HEADING = "Code in the active code cell:"
18
- ACTIVE_CELL_ID_SECTION_HEADING = "The ID of the active code cell:"
19
- ACTIVE_CELL_OUTPUT_SECTION_HEADING = "Output of the active code cell:"
20
- GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING = "Output of the code cell you just applied the CELL_UPDATE to:"
21
- JUPYTER_NOTEBOOK_SECTION_HEADING = "Jupyter Notebook:"
22
- STREAMLIT_APP_STATUS_SECTION_HEADING = "Streamlit App Status:"
23
-
24
- # Placeholder text used when trimming content from messages
25
- CONTENT_REMOVED_PLACEHOLDER = "Content removed to save space"
26
-
27
- CITATION_RULES = """RULES FOR CITING YOUR WORK
28
-
14
+ CITATION_RULES = """
29
15
  It is important that the user is able to verify any insights that you share with them about their data. To make this easy for the user, you must cite the lines of code that you are drawing the insight from. To provide a citation, use one of the following formats inline in your response:
30
16
 
31
17
  Single line citation:
@@ -36,19 +22,18 @@ Multiline citation (for citing a range of lines):
36
22
 
37
23
  Citation Rules:
38
24
 
39
- 1. Every fact or statement derived from the user's notebook must include a citation.
25
+ 1. Every fact or conclusion you draw based on the user's notebook must include a MITO_CITATION so that the user can verify your work.
40
26
  2. When choosing the citation, select the code that will most help the user validate the fact or statement that you shared with them.
41
27
  3. Place the citation immediately after the statement it supports. Do not explain the citation with phrases like "See", "Derived from", etc. Just provide the citation object.
42
28
  4. For the "line_number" field, use the line number within the cell that is most relevant to the citation. Important: The cell line number should be 0-indexed and should not skip comments.
43
29
  5. For multiline citations, use the "first_line-last_line" format when the insight spans multiple lines of code. Both line numbers should be 0-indexed.
44
30
  6. If you cannot find relevant information in the notebook to answer a question, clearly state this and do not provide a citation.
45
- 7. You ONLY need to provide a citation when sharing an insight from the data in the message part of the response. If all you are doing is writing/updating code, then there is no need to provide a citation.
31
+ 7. You only need to provide a citation when sharing an insight with the user. For example you should use a MITO_CITATION when you writing insights like any of the following: "The highest trading volume day was on January 15th, 2025", "The MSE was 6.8", "Apple's COGS are represented by the Orange bar in the graph". If you are not writing a quantitative insight and instead are just referring to a block of code like "I updated Cell 5 to include a graph", then you should instead use a MITO_CELL_REF.
46
32
  8. Do not include the citation in the code block as a comment. ONLY include the citation in the message field of your response.
47
33
  """
48
34
 
49
- CELL_REFERENCE_RULES = """RULES FOR REFERENCING CELLS
50
-
51
- When referring to specific cells in the notebook in your messages, use cell references so the user can easily navigate to the cell you're talking about. The user sees cells numbered as "Cell 1", "Cell 2", etc., but internally cells are identified by their unique IDs.
35
+ CELL_REFERENCE_RULES = """
36
+ When referring to specific cells in the notebook in your messages, use cell references so the user can easily navigate to the cell you're talking about. Cell references are displayed to the user "Cell 1", "Cell 2", etc., but internally cells are identified by their unique IDs.
52
37
 
53
38
  To reference a cell, use this format inline in your message:
54
39
  [MITO_CELL_REF:cell_id]
@@ -58,35 +43,12 @@ This will be displayed to the user as a clickable "Cell N" link that navigates t
58
43
  Cell Reference Rules:
59
44
 
60
45
  1. Use cell references when discussing specific cells you've created or modified (e.g., "I've added the data cleaning code in [MITO_CELL_REF:abc123]").
61
- 2. Use cell references when referring to cells the user mentioned or that contain relevant context.
62
- 3. The cell_id must be an actual cell ID from the notebook - do not make up IDs.
63
- 4. Place the reference inline where it makes sense in your message, similar to how you would write "Cell 3" in natural language.
64
- 5. Do not use cell references in code - only in the message field of your responses.
65
- 6. Cell references are different from citations. Use citations for specific line-level insights; use cell references for general cell-level navigation.
66
-
67
- Example:
68
- "I've loaded the sales data in [MITO_CELL_REF:c68fdf19-db8c-46dd-926f-d90ad35bb3bc] and will now calculate the monthly totals."
46
+ 2. The cell_id must be an actual cell ID from the notebook - do not make up IDs.
47
+ 3. Place the reference inline where it makes sense in your message, similar to how you would write "Cell 3" in natural language.
48
+ 4. Do not use cell references in code - only in the message field of your responses.
49
+ 5. You only need to provide a cell reference when you want to make it easy for the user to navigate to a specific cell in the notebook. For example you should use a MITO_CELL_REF when you are stating things like: "I've loaded the sales data in [MITO_CELL_REF:c68fdf19-db8c-46dd-926f-d90ad35bb3bc]" or "[MITO_CELL_REF:a91fde20-cc7f-g6ee-146g-e10bc34abdbh] creates the graph showing the total highest closing stock price for each company". If you are not referencing an entire code block and instead of providing justification for a specific conclucions that you drew like "The most common used car in the lot is a 2005 Honda CRV", then you should instead use a MITO_CITATION.
69
50
  """
70
51
 
71
- def get_active_cell_output_str(has_active_cell_output: bool) -> str:
72
- """
73
- Used to tell the AI about the output of the active code cell.
74
- We use this in the chat prompt.
75
- """
76
- if has_active_cell_output:
77
- return f"{ACTIVE_CELL_OUTPUT_SECTION_HEADING}\nAttatched is an image of the output of the active code cell for your context."
78
- else:
79
- return ""
80
-
81
- def cell_update_output_str(has_cell_update_output: bool) -> str:
82
- """
83
- Used to respond to the GET_CELL_OUTPUT tool, telling the agent the output of the cell it requested
84
- """
85
- if has_cell_update_output:
86
- return f"{GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING}\nAttatched is an image of code cell output that you requested."
87
- else:
88
- return ""
89
-
90
52
  def redact_sensitive_info(connections: dict) -> dict:
91
53
  """
92
54
  Redacts sensitive information from connections data.
@@ -184,7 +146,7 @@ Here is the schema:
184
146
  return DATABASE_RULES
185
147
 
186
148
 
187
- CHAT_CODE_FORMATTING_RULES = """CRITICAL CODE UPDATE RULES:
149
+ CHAT_CODE_FORMATTING_RULES = """
188
150
  - COMPLETE REPLACEMENT: Your code will COMPLETELY REPLACE the entire contents of the active code cell.
189
151
  - INCLUDE ALL CODE: You MUST return the COMPLETE, FULL contents of the entire code cell - including ALL existing code that should remain plus your modifications.
190
152
  - NEVER PARTIAL CODE: NEVER return only a portion, snippet, or subset of the code cell. Partial responses will break the user's notebook by deleting important code.
@@ -0,0 +1,70 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from mito_ai.completions.prompt_builders.prompt_section_registry.base import PromptSection
5
+
6
+
7
+ from typing import List, Type
8
+ from .files import FilesSection
9
+ from .variables import VariablesSection
10
+ from .active_cell_code import ActiveCellCodeSection
11
+ from .notebook import NotebookSection
12
+ from .active_cell_id import ActiveCellIdSection
13
+ from .active_cell_output import ActiveCellOutputSection
14
+ from .get_cell_output_tool_response import GetCellOutputToolResponseSection
15
+ from .streamlit_app_status import StreamlitAppStatusSection
16
+ from .selected_context import SelectedContextSection
17
+ from .rules import RulesSection
18
+ from .task import TaskSection
19
+ from .error_traceback import ErrorTracebackSection
20
+ from .example import ExampleSection
21
+ from .generic import GenericSection
22
+ from .base import Prompt, PromptSection
23
+
24
+
25
+ class SectionRegistry:
26
+ """Namespace for easy section construction."""
27
+ Files = FilesSection
28
+ Variables = VariablesSection
29
+ ActiveCellCode = ActiveCellCodeSection
30
+ Notebook = NotebookSection
31
+ ActiveCellId = ActiveCellIdSection
32
+ ActiveCellOutput = ActiveCellOutputSection
33
+ GetCellOutputToolResponse = GetCellOutputToolResponseSection
34
+ StreamlitAppStatus = StreamlitAppStatusSection
35
+ SelectedContext = SelectedContextSection
36
+ Rules = RulesSection
37
+ Task = TaskSection
38
+ ErrorTraceback = ErrorTracebackSection
39
+ Example = ExampleSection
40
+ Generic = GenericSection
41
+
42
+
43
+ # Export as SG (Section Generator) for easy usage
44
+ SG = SectionRegistry()
45
+
46
+ # Also export function to get all section classes for trimming
47
+ def get_all_section_classes() -> List[Type[PromptSection]]:
48
+ """Returns all section classes for building trimming mapping."""
49
+ prompt_section_classes: List[Type[PromptSection]] = PromptSection.__subclasses__()
50
+ return prompt_section_classes
51
+
52
+
53
+ def get_max_trim_after_messages() -> int:
54
+ """
55
+ Returns the maximum trim_after_messages value from all section classes.
56
+
57
+ This is used for cache boundary calculation - we cache messages that are
58
+ older than the max trim threshold, since those messages are stable and
59
+ won't be edited anymore.
60
+
61
+ Returns 0 if all sections have trim_after_messages = None.
62
+ """
63
+ section_classes = get_all_section_classes()
64
+ max_value = 0
65
+ for section_class in section_classes:
66
+ trim_value = getattr(section_class, 'trim_after_messages', None)
67
+ if trim_value is not None and trim_value > max_value:
68
+ max_value = trim_value
69
+ return max_value
70
+
@@ -0,0 +1,15 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+
6
+
7
+ class ActiveCellCodeSection(PromptSection):
8
+ """Section for code in the active code cell."""
9
+ trim_after_messages: int = 3
10
+
11
+ def __init__(self, code: str):
12
+ self.code = code
13
+ self.content = f"```python\n{code}\n```"
14
+ self.name = "ActiveCellCode"
15
+
@@ -0,0 +1,10 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+
6
+
7
+ class ActiveCellIdSection(PromptSection):
8
+ """Section for the ID of the active code cell."""
9
+ trim_after_messages = None
10
+
@@ -0,0 +1,20 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+
6
+
7
+ class ActiveCellOutputSection(PromptSection):
8
+ """Section for output of the active code cell."""
9
+ trim_after_messages: int = 3
10
+ exclude_if_empty: bool = True
11
+
12
+ def __init__(self, has_active_cell_output: bool):
13
+ self.name = "ActiveCellOutput"
14
+ self.has_active_cell_output = has_active_cell_output
15
+ self.content = ""
16
+ if has_active_cell_output:
17
+ # The actual image is attatched to the message, its not part of the text content
18
+ self.content = f"Attatched is an image of the output of the active code cell."
19
+
20
+
@@ -0,0 +1,37 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from abc import ABC
5
+ from typing import Optional, List
6
+
7
+
8
+ class PromptSection(ABC):
9
+ """Abstract base class for all prompt sections."""
10
+
11
+ # Class variable: trimming threshold (None = never trim)
12
+ trim_after_messages: Optional[int] = 3
13
+
14
+ # Class variable: if True, exclude XML tags when content is empty
15
+ exclude_if_empty: bool = False
16
+
17
+ def __init__(self, content: str):
18
+ self.content = content
19
+ self.name = self.__class__.__name__.replace("Section", "")
20
+
21
+ def __str__(self) -> str:
22
+ # If exclude_if_empty is True and content is empty, return empty string
23
+ if self.exclude_if_empty and (self.content is None or self.content.strip() == ""):
24
+ return ""
25
+ return f"<{self.name}>\n{self.content}\n</{self.name}>"
26
+
27
+
28
+ class Prompt:
29
+ """Container for multiple prompt sections."""
30
+ def __init__(self, sections: List[PromptSection]):
31
+ self.sections = sections
32
+
33
+ def __str__(self) -> str:
34
+ # Filter out empty strings to exclude sections that return "" when they have no content
35
+ section_strings = [str(section) for section in self.sections]
36
+ return "\n\n".join(s for s in section_strings if s)
37
+
@@ -0,0 +1,17 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+ from typing import Optional
6
+
7
+
8
+ class ErrorTracebackSection(PromptSection):
9
+ """Section for error traceback - never trimmed."""
10
+ trim_after_messages: Optional[int] = None
11
+
12
+ def __init__(self, code_cell_id: str, traceback: str):
13
+ self.code_cell_id = code_cell_id
14
+ self.traceback = traceback
15
+ self.content = f"Cell ID: {code_cell_id}\n\n{traceback}"
16
+ self.name = "Error"
17
+
@@ -0,0 +1,19 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+
6
+
7
+ class ExampleSection(PromptSection):
8
+ """Section for examples - contains nested section XML tags."""
9
+ trim_after_messages: int = 3
10
+
11
+ def __init__(self, name: str, content: str):
12
+ # content can contain nested XML like: "<Files>...</Files><Variables>...</Variables>"
13
+ super().__init__(content)
14
+ self.example_name = name # e.g., "Example 1", "Cell Modification Example"
15
+
16
+ def __str__(self) -> str:
17
+ # Render as: <Example name="Example 1">...</Example>
18
+ return f'<Example name="{self.example_name}">{self.content}</Example>'
19
+
@@ -0,0 +1,17 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import List, Optional
5
+ from .base import PromptSection
6
+
7
+
8
+ class FilesSection(PromptSection):
9
+ """Section for files in the current directory."""
10
+ trim_after_messages: int = 3
11
+ exclude_if_empty: bool = True
12
+
13
+ def __init__(self, files: Optional[List[str]]):
14
+ self.files = files
15
+ self.content = '\n'.join([f"file_name: {file}" for file in files or []])
16
+ self.name = "Files"
17
+
@@ -0,0 +1,15 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+ from typing import Optional
6
+
7
+
8
+ class GenericSection(PromptSection):
9
+ """Generic section that can be used with a custom name and content."""
10
+ trim_after_messages: Optional[int] = None
11
+
12
+ def __init__(self, name: str, content: str):
13
+ self.content = content
14
+ self.name = name
15
+
@@ -0,0 +1,21 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import Optional
5
+ from .base import PromptSection
6
+
7
+
8
+ class GetCellOutputToolResponseSection(PromptSection):
9
+ """Section for output of the code cell after applying CELL_UPDATE."""
10
+ trim_after_messages: int = 3
11
+ exclude_if_empty: bool = True
12
+
13
+ def __init__(self, base64EncodedCellOutput: Optional[str]):
14
+ self.name = "GetCellOutputToolResponse"
15
+ self.base64EncodedCellOutput = base64EncodedCellOutput
16
+
17
+ self.content = ""
18
+ if base64EncodedCellOutput is not None and base64EncodedCellOutput != '':
19
+ # The actual image is attatched to the message, its not part of the text content
20
+ self.content = f"Attatched is an image of code cell output that you requested."
21
+
@@ -0,0 +1,19 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import List
5
+
6
+ from mito_ai.completions.models import AIOptimizedCell
7
+ from .base import PromptSection
8
+
9
+
10
+ class NotebookSection(PromptSection):
11
+ """Section for Jupyter notebook content."""
12
+ trim_after_messages: int = 6
13
+ exclude_if_empty: bool = False
14
+
15
+ def __init__(self, cells: List[AIOptimizedCell]):
16
+ self.cells = cells
17
+ self.content = '\n'.join([f"{cell}" for cell in cells or []])
18
+ self.name = "Notebook"
19
+
@@ -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
+ from .base import PromptSection
5
+ from typing import Optional, List, Dict
6
+ from mito_ai.rules.utils import get_rule
7
+
8
+ def get_rules_str(additional_context: Optional[List[Dict[str, str]]]) -> str:
9
+ """
10
+ Extract the rules from the additional context array, and retrieve the rule content.
11
+ """
12
+ if not additional_context:
13
+ return ""
14
+
15
+ selected_rules = [context["value"] for context in additional_context if context.get("type") == "rule"]
16
+ if len(selected_rules) == 0:
17
+ return ""
18
+
19
+ rules_content = []
20
+ for rule in selected_rules:
21
+ rule_content = get_rule(rule)
22
+ if rule_content is None or rule_content == "":
23
+ continue
24
+
25
+ rules_content.append(f"{rule}:\n\n{rule_content}")
26
+
27
+ return '\n'.join(rules_content)
28
+
29
+
30
+ class RulesSection(PromptSection):
31
+ """Section for rules - never trimmed."""
32
+ trim_after_messages: Optional[int] = None
33
+ exclude_if_empty: bool = True
34
+
35
+ def __init__(self, additional_context: Optional[List[Dict[str, str]]]):
36
+ self.additional_context = additional_context
37
+ self.content = get_rules_str(additional_context)
38
+ self.name = "Rules"
39
+
@@ -1,91 +1,100 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
- from typing import List, Optional, Dict
5
- from mito_ai.rules.utils import get_rule
6
-
7
-
8
- def get_rules_str(additional_context: Optional[List[Dict[str, str]]]) -> str:
9
- """
10
- Extract the rules from the additional context array, and retrieve the rule content.
11
- """
12
- if not additional_context:
13
- return ""
14
-
15
- selected_rules = [context["value"] for context in additional_context if context.get("type") == "rule"]
16
- if len(selected_rules) == 0:
17
- return ""
18
-
19
- rules_str = ""
20
- for rule in selected_rules:
21
- rule_content = get_rule(rule)
22
- if rule_content is None or rule_content == "":
23
- continue
24
-
25
- rules_str += f"===========\n\nCustom Instructions Provided by User: {rule}\n\n{rule_content}\n\n==========="
26
-
27
- return rules_str
28
-
4
+ from .base import PromptSection
5
+ from typing import Optional, List, Dict
6
+ import json
29
7
 
30
8
  def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]]) -> str:
31
9
  """
32
- Get the selected context from the additional context array.
10
+ Render the selected context from the additional context array.
33
11
  """
12
+
34
13
  if not additional_context:
35
14
  return ""
36
-
15
+
37
16
  # STEP 1: Extract each context type into a separate list
17
+ # Filter out any non-dict items and ensure we only process dictionaries
38
18
  selected_variables = [context["value"] for context in additional_context if context.get("type") == "variable"]
39
19
  selected_files = [context["value"] for context in additional_context if context.get("type") == "file"]
40
20
  selected_db_connections = [context["value"] for context in additional_context if context.get("type") == "db"]
41
21
  selected_images = [context["value"] for context in additional_context if context.get("type", "").startswith("image/")]
42
22
  selected_cells = [context["value"] for context in additional_context if context.get("type") == "cell"]
23
+ selected_line_selections = [context["value"] for context in additional_context if context.get("type") == "line_selection"]
43
24
 
44
25
  # STEP 2: Create a list of strings (instructions) for each context type
45
26
  context_parts = []
46
-
27
+
47
28
  if len(selected_variables) > 0:
48
29
  context_parts.append(
49
30
  "The following variables have been selected by the user to be used in the task:\n"
50
31
  + "\n".join(selected_variables)
51
32
  )
52
-
33
+
53
34
  if len(selected_files) > 0:
54
35
  context_parts.append(
55
36
  "The following files have been selected by the user to be used in the task:\n"
56
37
  + "\n".join(selected_files)
57
38
  )
58
-
39
+
59
40
  if len(selected_db_connections) > 0:
60
41
  context_parts.append(
61
42
  "The following database connections have been selected by the user to be used in the task:\n"
62
43
  + "\n".join(selected_db_connections)
63
44
  )
64
-
45
+
65
46
  if len(selected_images) > 0:
66
47
  context_parts.append(
67
48
  "The following images have been selected by the user to be used in the task:\n"
68
49
  + "\n".join(selected_images)
69
50
  )
70
-
51
+
71
52
  if len(selected_cells) > 0:
72
53
  context_parts.append(
73
54
  "The following cells have been selected by the user to be used in the task:\n"
74
55
  + "\n".join(selected_cells)
75
56
  )
76
57
 
58
+ if len(selected_line_selections) > 0:
59
+ # Parse the line selection JSON values and format them for the prompt
60
+ line_selection_strs = []
61
+ for line_selection_json in selected_line_selections:
62
+ try:
63
+ selection_info = json.loads(line_selection_json)
64
+ cell_id = selection_info.get("cellId", "")
65
+ start_line = selection_info.get("startLine", 0)
66
+ end_line = selection_info.get("endLine", 0)
67
+ selected_code = selection_info.get("selectedCode", "")
68
+
69
+ # Format: Cell {cell_id} lines X-Y (0 indexed)\n[selected code]
70
+ if start_line == end_line:
71
+ line_info = f"Cell {cell_id} line {start_line} (0 indexed)"
72
+ else:
73
+ line_info = f"Cell {cell_id} lines {start_line}-{end_line} (0 indexed)"
74
+
75
+ line_selection_strs.append(f"{line_info}\n```python\n{selected_code}\n```")
76
+ except (json.JSONDecodeError, KeyError):
77
+ continue
78
+
79
+ if line_selection_strs:
80
+ context_parts.append(
81
+ "The user has selected the following lines of code to focus on:\n"
82
+ + "\n\n".join(line_selection_strs)
83
+ )
84
+
77
85
  # STEP 3: Combine into a single string
78
86
  return "\n\n".join(context_parts)
79
87
 
80
88
 
81
- def get_streamlit_app_status_str(notebook_id: str, notebook_path: str) -> str:
82
- """
83
- Get the streamlit app status string.
84
- """
85
- from mito_ai.path_utils import does_notebook_id_have_corresponding_app
86
- if does_notebook_id_have_corresponding_app(notebook_id, notebook_path):
87
- return "The notebook has an existing Streamlit app that you can edit"
88
- return "The notebook does not have an existing Streamlit app. If you want to show an app to the user, you must create a new one."
89
-
90
-
89
+ class SelectedContextSection(PromptSection):
90
+ """Section for selected context - never trimmed."""
91
+ trim_after_messages: Optional[int] = None
92
+ exclude_if_empty: bool = True
93
+
94
+ def __init__(self, additional_context: Optional[List[Dict[str, str]]]):
95
+ self.additional_context = additional_context
96
+ self.content = get_selected_context_str(additional_context)
97
+ self.name = "SelectedContext"
98
+
99
+
91
100
 
@@ -0,0 +1,25 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+
6
+ def get_streamlit_app_status_str(notebook_id: str, notebook_path: str) -> str:
7
+ """
8
+ Get the streamlit app status string.
9
+ """
10
+ from mito_ai.path_utils import does_notebook_id_have_corresponding_app
11
+ if does_notebook_id_have_corresponding_app(notebook_id, notebook_path):
12
+ return "The notebook has an existing Streamlit app that you can edit"
13
+ return "The notebook does not have an existing Streamlit app. If you want to show an app to the user, you must create a new one."
14
+
15
+
16
+ class StreamlitAppStatusSection(PromptSection):
17
+ """Section for Streamlit app status."""
18
+ trim_after_messages: int = 3
19
+
20
+ def __init__(self, notebook_id: str, notebook_path: str):
21
+ self.notebook_id = notebook_id
22
+ self.notebook_path = notebook_path
23
+ self.content = get_streamlit_app_status_str(notebook_id, notebook_path)
24
+ self.name = "StreamlitAppStatus"
25
+
@@ -0,0 +1,12 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .base import PromptSection
5
+ from typing import Optional
6
+
7
+
8
+ class TaskSection(PromptSection):
9
+ """Section for user task - never trimmed."""
10
+ trim_after_messages: Optional[int] = None
11
+ exclude_if_empty: bool = False
12
+
@@ -0,0 +1,18 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import List, Optional
5
+ from .base import PromptSection
6
+
7
+
8
+ class VariablesSection(PromptSection):
9
+ """Section for defined variables."""
10
+ trim_after_messages: int = 6
11
+ exclude_if_empty: bool = False
12
+
13
+ def __init__(self, variables: Optional[List[str]]):
14
+ self.variables = variables
15
+ self.content = '\n'.join([f"{variable}" for variable in variables or []])
16
+ self.name = "Variables"
17
+
18
+