Skip to content

Commit 46bd376

Browse files
feat: add errors_summary prompt for easy injection of all notebook errors (#6995)
## 📝 Summary <!-- Provide a concise summary of what this pull request is addressing. If this PR fixes any issues, list them here by number (e.g., Fixes #123). --> This adds errors_summary prompt so users can easily inject all errors in notebooks into agents or Cursor easily divided into notebooks and cells (with session_id and file path data included for each notebook): <img width="577" height="346" alt="Screenshot 2025-10-29 at 6 39 29 PM" src="https://github.com/user-attachments/assets/69717a7e-54cd-45b2-b90b-10b7dd81bfce" /> <img width="577" height="1025" alt="Screenshot 2025-10-29 at 6 42 19 PM" src="https://github.com/user-attachments/assets/4bf94da8-cd57-42be-8575-fb0b7ac14eb1" /> ## 🔍 Description of Changes <!-- Detail the specific changes made in this pull request. Explain the problem addressed and how it was resolved. If applicable, provide before and after comparisons, screenshots, or any relevant details to help reviewers understand the changes easily. --> This also: - DRYs up getting errors from notebooks and cells so that GetRuntimeCellData, GetNotebookErrors, and ErrorsSummary prompt all pull from tool context with errors that are of the same structure - Fixes tests for GetNotebookErrors - Removes redundant tests in test_cells.py - Adds "include_stderr" to manage verbosiity of prompt - Adds clean_output utility function to the stderr of all tools ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 79ecae2 commit 46bd376

File tree

11 files changed

+618
-422
lines changed

11 files changed

+618
-422
lines changed

marimo/_ai/_tools/base.py

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@
1717
)
1818

1919
from marimo import _loggers
20-
from marimo._ai._tools.types import MarimoNotebookInfo, ToolGuidelines
20+
from marimo._ai._tools.types import (
21+
MarimoCellConsoleOutputs,
22+
MarimoCellErrors,
23+
MarimoErrorDetail,
24+
MarimoNotebookInfo,
25+
ToolGuidelines,
26+
)
2127
from marimo._ai._tools.utils.exceptions import ToolExecutionError
28+
from marimo._ai._tools.utils.output_cleaning import clean_output
2229
from marimo._config.config import CopilotMode
30+
from marimo._messaging.cell_output import CellChannel
31+
from marimo._messaging.ops import CellOp
2332
from marimo._server.ai.tools.types import (
2433
FunctionArgs,
2534
ToolDefinition,
@@ -28,7 +37,7 @@
2837
from marimo._server.api.deps import AppStateBase
2938
from marimo._server.model import ConnectionState
3039
from marimo._server.sessions import Session, SessionManager
31-
from marimo._types.ids import SessionId
40+
from marimo._types.ids import CellId_t, SessionId
3241
from marimo._utils.case import to_snake_case
3342
from marimo._utils.dataclass_to_openapi import PythonTypeToOpenAPI
3443
from marimo._utils.parse_dataclass import parse_raw
@@ -81,6 +90,18 @@ def get_session(self, session_id: SessionId) -> Session:
8190
)
8291
return session_manager.sessions[session_id]
8392

93+
def get_cell_ops(self, session_id: SessionId, cell_id: CellId_t) -> CellOp:
94+
session_view = self.get_session(session_id).session_view
95+
if cell_id not in session_view.cell_operations:
96+
raise ToolExecutionError(
97+
f"Cell operation not found for cell {cell_id}",
98+
code="CELL_OPERATION_NOT_FOUND",
99+
is_retryable=False,
100+
suggested_fix="Try again with a valid cell ID.",
101+
meta={"cell_id": cell_id},
102+
)
103+
return session_view.cell_operations[cell_id]
104+
84105
def get_active_sessions_internal(self) -> list[MarimoNotebookInfo]:
85106
"""
86107
Get active sessions from the app state.
@@ -113,6 +134,130 @@ def get_active_sessions_internal(self) -> list[MarimoNotebookInfo]:
113134
# Return most recent notebooks first (reverse chronological order)
114135
return files[::-1]
115136

137+
def get_notebook_errors(
138+
self, session_id: SessionId, include_stderr: bool
139+
) -> list[MarimoCellErrors]:
140+
"""
141+
Get all errors in the current notebook session, organized by cell.
142+
143+
Optionally include stderr messages foreach cell.
144+
"""
145+
session = self.get_session(session_id)
146+
session_view = session.session_view
147+
cell_errors_map: dict[CellId_t, MarimoCellErrors] = {}
148+
notebook_errors: list[MarimoCellErrors] = []
149+
stderr: list[str] = []
150+
151+
for cell_id, cell_op in session_view.cell_operations.items():
152+
errors = self.get_cell_errors(
153+
session_id,
154+
cell_id,
155+
maybe_cell_op=cell_op,
156+
)
157+
if include_stderr:
158+
stderr = self.get_cell_console_outputs(cell_op).stderr
159+
if errors:
160+
cell_errors_map[cell_id] = MarimoCellErrors(
161+
cell_id=cell_id,
162+
errors=errors,
163+
stderr=stderr,
164+
)
165+
166+
# Use cell_manager to get cells in the correct notebook order
167+
cell_manager = session.app_file_manager.app.cell_manager
168+
for cell_data in cell_manager.cell_data():
169+
cell_id = cell_data.cell_id
170+
if cell_id in cell_errors_map:
171+
notebook_errors.append(cell_errors_map[cell_id])
172+
173+
return notebook_errors
174+
175+
def get_cell_errors(
176+
self,
177+
session_id: SessionId,
178+
cell_id: CellId_t,
179+
maybe_cell_op: Optional[CellOp] = None,
180+
) -> list[MarimoErrorDetail]:
181+
"""
182+
Get all errors for a given cell.
183+
"""
184+
errors: list[MarimoErrorDetail] = []
185+
cell_op = maybe_cell_op or self.get_cell_ops(session_id, cell_id)
186+
187+
if (
188+
not cell_op.output
189+
or cell_op.output.channel != CellChannel.MARIMO_ERROR
190+
):
191+
return errors
192+
193+
items = cell_op.output.data
194+
195+
if not isinstance(items, list):
196+
# no errors
197+
return errors
198+
199+
for err in items:
200+
# TODO: filter out noisy useless errors
201+
# like "An ancestor raised an exception..."
202+
if isinstance(err, dict):
203+
errors.append(
204+
MarimoErrorDetail(
205+
type=err.get("type", "UnknownError"),
206+
message=err.get("msg", str(err)),
207+
traceback=err.get("traceback", []),
208+
)
209+
)
210+
else:
211+
# Fallback for rich error objects
212+
err_type: str = getattr(err, "type", type(err).__name__)
213+
describe_fn: Optional[Any] = getattr(err, "describe", None)
214+
message_val = (
215+
describe_fn() if callable(describe_fn) else str(err)
216+
)
217+
message: str = str(message_val)
218+
tb: list[str] = getattr(err, "traceback", []) or []
219+
errors.append(
220+
MarimoErrorDetail(
221+
type=err_type,
222+
message=message,
223+
traceback=tb,
224+
)
225+
)
226+
227+
return errors
228+
229+
def get_cell_console_outputs(
230+
self, cell_op: CellOp
231+
) -> MarimoCellConsoleOutputs:
232+
"""
233+
Get the console outputs for a given cell operation.
234+
"""
235+
stdout_messages: list[str] = []
236+
stderr_messages: list[str] = []
237+
238+
if cell_op.console is None:
239+
return MarimoCellConsoleOutputs(stdout=[], stderr=[])
240+
241+
console_outputs = (
242+
cell_op.console
243+
if isinstance(cell_op.console, list)
244+
else [cell_op.console]
245+
)
246+
for output in console_outputs:
247+
if output is None:
248+
continue
249+
elif output.channel == CellChannel.STDOUT:
250+
stdout_messages.append(str(output.data))
251+
elif output.channel == CellChannel.STDERR:
252+
stderr_messages.append(str(output.data))
253+
254+
cleaned_stdout_messages = clean_output(stdout_messages)
255+
cleaned_stderr_messages = clean_output(stderr_messages)
256+
257+
return MarimoCellConsoleOutputs(
258+
stdout=cleaned_stdout_messages, stderr=cleaned_stderr_messages
259+
)
260+
116261

117262
class ToolBase(Generic[ArgsT, OutT], ABC):
118263
"""

0 commit comments

Comments
 (0)