jupyter-agent 2025.6.100__py3-none-any.whl → 2025.6.101__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 (35) hide show
  1. jupyter_agent/__init__.py +0 -0
  2. jupyter_agent/bot_agents/__init__.py +42 -0
  3. jupyter_agent/bot_agents/base.py +324 -0
  4. jupyter_agent/bot_agents/master_planner.py +45 -0
  5. jupyter_agent/bot_agents/output_task_result.py +29 -0
  6. jupyter_agent/bot_agents/task_code_executor.py +53 -0
  7. jupyter_agent/bot_agents/task_coder.py +71 -0
  8. jupyter_agent/bot_agents/task_debuger.py +69 -0
  9. jupyter_agent/bot_agents/task_planner_v1.py +158 -0
  10. jupyter_agent/bot_agents/task_planner_v2.py +172 -0
  11. jupyter_agent/bot_agents/task_planner_v3.py +189 -0
  12. jupyter_agent/bot_agents/task_reasoner.py +61 -0
  13. jupyter_agent/bot_agents/task_structrue_reasoner.py +106 -0
  14. jupyter_agent/bot_agents/task_structrue_summarier.py +123 -0
  15. jupyter_agent/bot_agents/task_summarier.py +76 -0
  16. jupyter_agent/bot_agents/task_verifier.py +99 -0
  17. jupyter_agent/bot_agents/task_verify_summarier.py +134 -0
  18. jupyter_agent/bot_chat.py +218 -0
  19. jupyter_agent/bot_contexts.py +466 -0
  20. jupyter_agent/bot_flows/__init__.py +20 -0
  21. jupyter_agent/bot_flows/base.py +209 -0
  22. jupyter_agent/bot_flows/master_planner.py +16 -0
  23. jupyter_agent/bot_flows/task_executor_v1.py +86 -0
  24. jupyter_agent/bot_flows/task_executor_v2.py +84 -0
  25. jupyter_agent/bot_flows/task_executor_v3.py +89 -0
  26. jupyter_agent/bot_magics.py +127 -0
  27. jupyter_agent/bot_outputs.py +480 -0
  28. jupyter_agent/utils.py +138 -0
  29. {jupyter_agent-2025.6.100.dist-info → jupyter_agent-2025.6.101.dist-info}/METADATA +13 -7
  30. jupyter_agent-2025.6.101.dist-info/RECORD +33 -0
  31. jupyter_agent-2025.6.101.dist-info/top_level.txt +1 -0
  32. jupyter_agent-2025.6.100.dist-info/RECORD +0 -5
  33. jupyter_agent-2025.6.100.dist-info/top_level.txt +0 -1
  34. {jupyter_agent-2025.6.100.dist-info → jupyter_agent-2025.6.101.dist-info}/WHEEL +0 -0
  35. {jupyter_agent-2025.6.100.dist-info → jupyter_agent-2025.6.101.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,466 @@
1
+ """
2
+ Copyright (c) 2025 viewstar000
3
+
4
+ This software is released under the MIT License.
5
+ https://opensource.org/licenses/MIT
6
+ """
7
+
8
+ import os
9
+ import re
10
+ import json
11
+ import yaml
12
+ import time
13
+ import shlex
14
+ import argparse
15
+ import traceback
16
+ import nbformat
17
+
18
+
19
+ from typing import Optional
20
+ from enum import Enum
21
+ from pydantic import BaseModel, Field
22
+ from IPython.core.getipython import get_ipython
23
+ from .bot_outputs import _D, _I, _W, _E, _F, _A, ReplyType
24
+
25
+
26
+ class CellType(str, Enum):
27
+ CODE = "code"
28
+ MARKDOWN = "markdown"
29
+ PLANNING = "planning"
30
+ TASK = "task"
31
+
32
+
33
+ class CellContext:
34
+
35
+ def __init__(self, idx: int, cell: dict):
36
+ self.cell_idx = idx
37
+ self.cell_id = cell.get("id")
38
+ self.cell_type: Optional[CellType] = cell["cell_type"]
39
+ self.cell_source = cell["source"].strip()
40
+ self.cell_tags = set(cell.get("metadata", {}).get("tags", []))
41
+ if mo := re.match(r"^#\s*BOT_CONTEXT:(.+)$", self.cell_source.split("\n", 1)[0]):
42
+ context_tags = ["CTX_" + t.strip().upper() for t in mo.group(1).strip().split(",")]
43
+ self.cell_tags |= set(context_tags)
44
+ self.cell_source = self.cell_source.split("\n", 1)[1].strip()
45
+
46
+ @property
47
+ def type(self):
48
+ return self.cell_type
49
+
50
+ @property
51
+ def source(self):
52
+ return self.cell_source
53
+
54
+ @property
55
+ def is_code_context(self):
56
+ return (
57
+ self.cell_type == CellType.CODE or "CTX_CODE" in self.cell_tags
58
+ ) and "CTX_EXCLUDE" not in self.cell_tags
59
+
60
+ @property
61
+ def is_task_context(self):
62
+ return (
63
+ self.cell_type in (CellType.TASK, CellType.PLANNING, CellType.MARKDOWN) or "CTX_TASK" in self.cell_tags
64
+ ) and "CTX_EXCLUDE" not in self.cell_tags
65
+
66
+
67
+ class CodeCellContext(CellContext):
68
+ """任务单元格上下文类"""
69
+
70
+ max_output_size = 24 * 1024
71
+ max_result_size = 24 * 1024
72
+ max_error_size = 4 * 1024
73
+
74
+ def __init__(self, idx: int, cell: dict):
75
+ """初始化任务单元格上下文"""
76
+ super().__init__(idx, cell)
77
+ assert self.cell_type == CellType.CODE
78
+ self._cell_output = ""
79
+ self._cell_result = ""
80
+ self._cell_error = ""
81
+ self.load_cell_outputs(cell)
82
+
83
+ def get_cell_output(self):
84
+ """获取任务单元格的输出"""
85
+ if len(self._cell_output) > self.max_output_size:
86
+ half_size = self.max_output_size // 2
87
+ self._cell_output = self._cell_output[:half_size] + "..." + self._cell_output[-half_size:]
88
+ return self._cell_output
89
+
90
+ def set_cell_output(self, output):
91
+ """设置任务单元格的输出"""
92
+ self._cell_output = output
93
+ if len(self._cell_output) > self.max_output_size:
94
+ half_size = self.max_output_size // 2
95
+ self._cell_output = self._cell_output[:half_size] + "..." + self._cell_output[-half_size:]
96
+
97
+ cell_output = property(get_cell_output, set_cell_output)
98
+
99
+ def get_cell_result(self):
100
+ """获取任务单元格的结果"""
101
+ if len(self._cell_result) > self.max_result_size:
102
+ half_size = self.max_result_size // 2
103
+ self._cell_result = self._cell_result[:half_size] + "..." + self._cell_result[-half_size:]
104
+ return self._cell_result
105
+
106
+ def set_cell_result(self, result):
107
+ """设置任务单元格的结果"""
108
+ self._cell_result = result
109
+ if len(self._cell_result) > self.max_result_size:
110
+ half_size = self.max_result_size // 2
111
+ self._cell_result = self._cell_result[:half_size] + "..." + self._cell_result[-half_size:]
112
+
113
+ cell_result = property(get_cell_result, set_cell_result)
114
+
115
+ def get_cell_error(self):
116
+ """获取任务单元格的错误信息"""
117
+ if len(self._cell_error) > self.max_error_size:
118
+ half_size = self.max_error_size // 2
119
+ self._cell_error = self._cell_error[:half_size] + "..." + self._cell_error[-half_size:]
120
+ return self._cell_error
121
+
122
+ def set_cell_error(self, error):
123
+ """设置任务单元格的错误信息"""
124
+ self._cell_error = error
125
+ if len(self._cell_error) > self.max_error_size:
126
+ half_size = self.max_error_size // 2
127
+ self._cell_error = self._cell_error[:half_size] + "..." + self._cell_error[-half_size:]
128
+
129
+ cell_error = property(get_cell_error, set_cell_error)
130
+
131
+ def load_cell_outputs(self, cell):
132
+ """加载当前任务单元格的上下文"""
133
+ try:
134
+ self.cell_output = ""
135
+ self.cell_result = ""
136
+ self.cell_error = ""
137
+ for output in cell.get("outputs", []):
138
+ # Available output types: stream, error, execute_result, display_data
139
+ if output["output_type"] == "stream":
140
+ _D(f"CELL[{self.cell_idx}] Stream output: {output["name"]}:{repr(output['text'])[:50]}")
141
+ self.cell_output += output["name"] + ":\n" + output["text"] + "\n"
142
+ if output["output_type"] == "error":
143
+ _D(f"CELL[{self.cell_idx}] Error output: {output.get("ename", "")} {output.get('evalue', "")}")
144
+ self.cell_error += output.get("ename", "") + ": " + output.get("evalue", "") + "\n"
145
+ if "traceback" in output:
146
+ self.cell_error += "Traceback:\n" + "\n".join(output.get("traceback", [])) + "\n"
147
+ if output["output_type"] == "execute_result":
148
+ output_data = output.get("data", {})
149
+ output_text = output_data.get("text/markdown") or output_data.get("text/plain")
150
+ _D(f"CELL[{self.cell_idx}] Execute result: {repr(output_text)[:50]}")
151
+ self.cell_result += output_text + "\n"
152
+ if output["output_type"] == "display_data":
153
+ output_meta = output.get("metadata", {})
154
+ if not output_meta.get("exclude_from_context", False):
155
+ output_data = output.get("data", {})
156
+ output_text = output_data.get("text/markdown") or output_data.get("text/plain")
157
+ reply_type = output_meta.get("reply_type")
158
+ if reply_type == ReplyType.CELL_ERROR:
159
+ _D(f"CELL[{self.cell_idx}] Display error data: {repr(output_text)[:50]}")
160
+ self.cell_error += output_text + "\n"
161
+ else:
162
+ _D(f"CELL[{self.cell_idx}] Display output data: {repr(output_text)[:50]}")
163
+ self.cell_output += output_text + "\n"
164
+ except Exception as e:
165
+ _W("Failed to load notebook cells {}: {}".format(type(e), str(e)))
166
+ _W(traceback.format_exc(limit=2))
167
+
168
+
169
+ class AgentData(BaseModel):
170
+ task_id: str = Field("", description="任务ID")
171
+ subject: str = Field("", description="任务目标")
172
+ coding_prompt: str = Field("", description="Agent编程提示")
173
+ verify_prompt: str = Field("", description="Agent验证提示")
174
+ summary_prompt: str = Field("", description="Agent总结提示")
175
+ issue: str = Field("", description="Agent验证不通过的问题")
176
+ result: str = Field("", description="Agent执行结果")
177
+ important_infos: Optional[dict] = Field(None, description="重要信息[JSON]")
178
+
179
+ @classmethod
180
+ def default(cls):
181
+ model_fields = getattr(cls, "model_fields", None)
182
+ if model_fields and hasattr(model_fields, "items"):
183
+ return {
184
+ name: field.examples[0] if getattr(field, "examples", None) else getattr(field, "default", None)
185
+ for name, field in model_fields.items()
186
+ }
187
+ else:
188
+ return {}
189
+
190
+
191
+ class AgentCellContext(CodeCellContext):
192
+ """任务单元格上下文类"""
193
+
194
+ SUPPORT_SAVE_META: bool = False
195
+
196
+ def __init__(self, idx: int, cell: dict):
197
+ """初始化任务单元格上下文"""
198
+ super().__init__(idx, cell)
199
+ self.agent_flow = None
200
+ self.agent_stage = None
201
+ self.magic_line, self.magic_code = self.cell_source.split("\n", 1)
202
+ self.magic_argv = shlex.split(self.magic_line)
203
+ self.magic_name = self.magic_argv[0]
204
+ self.magic_argv = self.magic_argv[1:]
205
+ self._remain_args = []
206
+ self._agent_data = AgentData.default()
207
+ self._cell_code = ""
208
+ self.parse_magic_argv()
209
+ self.load_result_from_outputs(cell)
210
+ self.load_data_from_metadata(cell)
211
+ self.load_data_from_source()
212
+
213
+ def get_source(self):
214
+ return self._cell_code
215
+
216
+ def set_source(self, value):
217
+ self._cell_code = value
218
+
219
+ source = property(get_source, set_source)
220
+
221
+ @property
222
+ def output(self):
223
+ return self.cell_output + "\n" + self.cell_result
224
+
225
+ @property
226
+ def result(self):
227
+ return self._agent_data["result"]
228
+
229
+ def __getattr__(self, name):
230
+ return self._agent_data[name]
231
+
232
+ def get_data(self, name):
233
+ return self._agent_data[name]
234
+
235
+ def set_data(self, name, value):
236
+ self._agent_data[name] = value
237
+
238
+ def parse_magic_argv(self):
239
+ """解析任务单元格的magic命令参数"""
240
+ parser = argparse.ArgumentParser()
241
+ parser.add_argument("-P", "--planning", action="store_true", default=False, help="Run in planning mode")
242
+ parser.add_argument("-f", "--flow", type=str, default=None, help="Task stage")
243
+ parser.add_argument("-s", "--stage", type=str, default=None, help="Task stage")
244
+ options, self._remain_args = parser.parse_known_args(self.magic_argv)
245
+ _D(
246
+ "CELL[{}] Magic Name: {}, Magic Args: {}, Remain Args: {}".format(
247
+ self.cell_idx, self.magic_name, options, self._remain_args
248
+ )
249
+ )
250
+ self.agent_flow = options.flow
251
+ self.agent_stage = options.stage
252
+ if options.planning and not self.agent_flow:
253
+ self.agent_flow = "planning"
254
+ if self.agent_flow and self.agent_flow.startswith("planning"):
255
+ self.cell_type = CellType.PLANNING
256
+ else:
257
+ self.cell_type = CellType.TASK
258
+
259
+ def load_data_from_source(self):
260
+ """解析任务单元格的选项"""
261
+ cell_options = ""
262
+ cell_code = ""
263
+ is_option = False
264
+ for line in self.magic_code.split("\n"):
265
+ if line.strip() == "## Task Options:":
266
+ is_option = True
267
+ continue
268
+ if line.strip() == "## ---":
269
+ is_option = False
270
+ continue
271
+ if is_option:
272
+ if line.startswith("# "):
273
+ cell_options += line[2:] + "\n"
274
+ else:
275
+ is_option = False
276
+ cell_code += line + "\n"
277
+ else:
278
+ cell_code += line + "\n"
279
+ self._cell_code = cell_code.strip()
280
+ _D("CELL[{}] Cell Options: {} ...".format(self.cell_idx, repr(cell_options)[:80]))
281
+ _D("CELL[{}] Cell Code: {} ...".format(self.cell_idx, repr(self._cell_code)[:80]))
282
+ if cell_options:
283
+ try:
284
+ cell_options = yaml.safe_load(cell_options)
285
+ for key, value in cell_options.items():
286
+ if key in self._agent_data:
287
+ if isinstance(self._agent_data[key], (dict, list)) and isinstance(value, str):
288
+ value = json.loads(value)
289
+ _D("CELL[{}] Load task option {}: {}".format(self.cell_idx, key, value))
290
+ self._agent_data[key] = value
291
+ except Exception as e:
292
+ _W("Failed to load task options {}: {}".format(type(e), str(e)))
293
+ _W(traceback.format_exc(limit=2))
294
+
295
+ def load_result_from_outputs(self, cell):
296
+ task_result = ""
297
+ for output in cell.get("outputs", []):
298
+ # Available output types: stream, error, execute_result, display_data
299
+ if output["output_type"] == "display_data":
300
+ output_data = output.get("data", {})
301
+ output_meta = output.get("metadata", {})
302
+ if output_meta.get("reply_type") == ReplyType.TASK_RESULT:
303
+ output_text = output_data.get("text/markdown") or output_data.get("text/plain")
304
+ _D(f"CELL[{self.cell_idx}] Task result: {repr(output_text)[:80]}")
305
+ task_result += "\n" + output_text
306
+ if task_result.strip():
307
+ self._agent_data["result"] = task_result
308
+
309
+ def load_data_from_metadata(self, cell):
310
+ agent_meta_infos = cell.get("metadata", {}).get("jupyter-agent-data", {})
311
+ _D("CELL[{}] Agent Meta Data: {}".format(self.cell_idx, repr(agent_meta_infos)[:80]))
312
+ for k, v in agent_meta_infos.items():
313
+ if k in self._agent_data:
314
+ _D(f"CELL[{self.cell_idx}] Load agent meta data: {k}: {repr(v)[:80]}")
315
+ self._agent_data[k] = v
316
+
317
+ def format_magic_line(self):
318
+ magic_args = ["%%bot"]
319
+ if self.agent_stage:
320
+ magic_args += ["-s", self.agent_stage]
321
+ if self.agent_flow:
322
+ if self.agent_flow == "planning":
323
+ magic_args += ["-P"]
324
+ else:
325
+ magic_args += ["-f", self.agent_flow]
326
+ magic_args += self._remain_args
327
+ magic_line = shlex.join(magic_args) + "\n"
328
+ return magic_line
329
+
330
+ def _format_yaml_element(self, e, level=0, indent=4):
331
+
332
+ space = " " * indent * level
333
+ result = ""
334
+ if isinstance(e, dict):
335
+ result += "\n"
336
+ for k, v in e.items():
337
+ if not v:
338
+ continue
339
+ result += f"{space}{k}: "
340
+ result += self._format_yaml_element(v, level + 1, indent)
341
+ elif isinstance(e, list):
342
+ result += "\n"
343
+ for v in e:
344
+ result += f"{space}- "
345
+ result += self._format_yaml_element(v, level + 1, indent)
346
+ elif isinstance(e, BaseModel):
347
+ result += self._format_yaml_element(e.model_dump(), level, indent)
348
+ elif isinstance(e, str):
349
+ if "\n" in e:
350
+ if e.endswith("\n"):
351
+ result += f"|\n"
352
+ else:
353
+ result += f"|-\n"
354
+ for line in e.split("\n"):
355
+ result += f"{space}{line}\n"
356
+ elif ":" in e or '"' in e or "'" in e:
357
+ result += f"'{e.replace("'", "''")}'\n"
358
+ else:
359
+ result += f"{e}\n"
360
+ elif e is None:
361
+ result += "null\n"
362
+ else:
363
+ result += f"{e}\n"
364
+ return result
365
+
366
+ def format_cell_options(self):
367
+ cell_options = {}
368
+ if self.SUPPORT_SAVE_META:
369
+ if self._agent_data.get("task_id"):
370
+ cell_options["task_id"] = self._agent_data["task_id"]
371
+ if self._agent_data.get("subject"):
372
+ cell_options["subject"] = self._agent_data["subject"]
373
+ else:
374
+ for key, value in self._agent_data.items():
375
+ if key == "result" and self.type == CellType.PLANNING:
376
+ continue
377
+ if value:
378
+ if (
379
+ isinstance(value, (dict, list))
380
+ and AgentData.model_fields[key]
381
+ and AgentData.model_fields[key].description is not None
382
+ and "[JSON]" in AgentData.model_fields[key].description # type: ignore
383
+ ):
384
+ value = json.dumps(value, ensure_ascii=False, indent=4)
385
+ cell_options[key] = value
386
+ if cell_options:
387
+ cell_options["update_time"] = time.strftime("%Y-%m-%d %H:%M:%S")
388
+ if cell_options:
389
+ result = "\n".join(f"# {line}" for line in self._format_yaml_element(cell_options).strip().split("\n"))
390
+ result = f"\n## Task Options:\n{result}\n## ---\n"
391
+ return result
392
+ return ""
393
+
394
+ def update_cell(self):
395
+ """生成Cell内容"""
396
+ _I("Updating Cell ...")
397
+ _A(**self._agent_data)
398
+ cell_source = ""
399
+ cell_source += self.format_magic_line()
400
+ cell_source += "\n" + self.format_cell_options()
401
+ cell_source += "\n" + self.source
402
+ ipython = get_ipython()
403
+ if ipython is not None:
404
+ _D("Updating Cell Source: {} ...".format(repr(cell_source)[:80]))
405
+ ipython.set_next_input(cell_source, replace=True)
406
+
407
+
408
+ class NotebookContext:
409
+ """Notebook上下文类"""
410
+
411
+ def __init__(self, cur_line, cur_content, notebook_path):
412
+ """初始化Notebook上下文"""
413
+ self.cur_line = cur_line.strip()
414
+ self.cur_content = cur_content.strip()
415
+ self.notebook_path = notebook_path
416
+ self.notebook_state = None
417
+ self._cells = []
418
+ self._current_cell = None
419
+
420
+ @property
421
+ def cells(self):
422
+ """获取当前cell之前的所有cell内容"""
423
+ try:
424
+ if (
425
+ not self._cells
426
+ or self.notebook_state is None
427
+ or self.notebook_state != os.stat(self.notebook_path).st_mtime
428
+ ):
429
+ _I(f"Loading Notebook Context: {self.notebook_path}")
430
+ with open(self.notebook_path, "r", encoding="utf-8") as f:
431
+ nb = nbformat.read(f, as_version=4)
432
+ self._cells = []
433
+ for idx, cell in enumerate(nb.cells):
434
+ _D(f"CELL[{idx}] {cell['cell_type']} {repr(cell['source'])[:80]}")
435
+ if cell["cell_type"] == "code":
436
+ if cell["source"].strip().startswith("%%bot"):
437
+ cell_ctx = AgentCellContext(idx, cell)
438
+ if (
439
+ self.cur_line.strip() == cell_ctx.magic_line[len(cell_ctx.magic_name) :].strip()
440
+ and self.cur_content.strip() == cell_ctx.magic_code.strip()
441
+ ):
442
+ if self._current_cell is None:
443
+ _I(f"CELL[{idx}] Reach current cell, RETURN!")
444
+ self._current_cell = cell_ctx
445
+ else:
446
+ _I(f"CELL[{idx}] Reach current cell, SKIP!")
447
+ break
448
+ else:
449
+ cell_ctx = CodeCellContext(idx, cell)
450
+ else:
451
+ cell_ctx = CellContext(idx, cell)
452
+ self._cells.append(cell_ctx)
453
+ self.notebook_state = os.stat(self.notebook_path).st_mtime
454
+ _I(f"Got {len(self._cells)} notebook cells")
455
+ except Exception as e:
456
+ _E("Failed to get notebook cells {}: {}".format(type(e), str(e)))
457
+ _E(traceback.format_exc(limit=2))
458
+ self._cells = []
459
+ return self._cells
460
+
461
+ @property
462
+ def cur_task(self):
463
+ """获取当前任务单元格的上下文"""
464
+ if self._current_cell is None:
465
+ len(self.cells)
466
+ return self._current_cell
@@ -0,0 +1,20 @@
1
+ """
2
+ Copyright (c) 2025 viewstar000
3
+
4
+ This software is released under the MIT License.
5
+ https://opensource.org/licenses/MIT
6
+ """
7
+
8
+ from .base import BaseTaskFlow
9
+ from .master_planner import MasterPlannerFlow
10
+ from .task_executor_v1 import TaskExecutorFlowV1
11
+ from .task_executor_v2 import TaskExecutorFlowV2
12
+ from .task_executor_v3 import TaskExecutorFlowV3
13
+
14
+ __all__ = [
15
+ "BaseTaskFlow",
16
+ "MasterPlannerFlow",
17
+ "TaskExecutorFlowV1",
18
+ "TaskExecutorFlowV2",
19
+ "TaskExecutorFlowV3",
20
+ ]