分段式创建和修改 Jupyter Notebook,支持图片嵌入与判题系统。
所有 Python 代码必须由 agent 自己通过 terminal 工具执行,严禁让用户手动在命令行执行。
原因:agent 通过工具执行命令时,stdout/stderr 会被自动捕获并返回,agent 可以依据输出判断操作是否成功。如果让用户手动执行,agent 无法感知执行结果,会导致工作流中断或状态不一致。
具体要求:
terminal 工具直接执行,不要输出代码让用户自己运行✅ 前缀的确认信息)terminal 工具执行 nb_info() 或 get_cell_summary() 检查当前 notebook 状态,再继续下一段text
用户请求
│
├─ 创建新 notebook → "创建流程"
├─ 修改现有 notebook → "修改流程"
├─ 添加判题系统 → "判题流程"
└─ 嵌入图片 → "图片流程"
<技能脚本目录> 占位符#示例代码中的 <技能脚本目录> 需要替换为此技能实际的 scripts/ 目录绝对路径。agent 应通过以下方式确定该路径:
scripts/ 子目录/scripts例如如果此 SKILL.md 位于 /home/user/.codebuddy/skills/notebook-builder/SKILL.md,则 <技能脚本目录> 应替换为 /home/user/.codebuddy/skills/notebook-builder/scripts。
此技能包含核心辅助脚本 scripts/nb_helpers.py,提供以下能力:
函数作用new_notebook()创建空 notebook 字典load_notebook(path)从文件加载 notebooksave_notebook(nb, path)保存 notebook 到文件make_markdown_cell(src)构造 Markdown cellmake_code_cell(src)构造 Code cellappend_cells(nb, cells)向末尾追加 cellinsert_cells(nb, idx, cells)在指定位置插入 celldelete_cells(nb, start, count)删除 cellreplace_cell(nb, idx, cell)替换 cellfind_cells_by_keyword(nb, kw)按关键词搜索 cellfind_cells_by_id(nb, id)按 id 查找 cellembed_image_in_markdown(path)图片转 base64 嵌入make_image_output(path)为 code cell 创建图片输出make_quiz_code_cell(...)生成哈希判题 cellmake_quiz_summary_cell(ids, total, scores)生成判题汇总 cellmerge_notebooks(paths, output)合并多个 notebookexport_to_script(nb, path)导出为 Python 脚本make_toc_cell(nb, max_level)自动生成目录 cellreorder_cells(nb, new_order)按索引重排 celltag_cell(cell, tags)为 cell 添加标签find_cells_by_tag(nb, tag)按标签查找 cellmake_section(title, ...)快速生成章节clear_all_outputs(nb)清除所有输出set_kernel(nb, name, display)修改 kernelnb_info(nb)notebook 统计信息get_cell_summary(nb)每个 cell 的摘要
分段生成的核心思路: 不要试图一次性生成整个 notebook。将内容拆分为多个"段",每段包含若干 cell,逐段追加。
初始化:用 new_notebook() 创建空 notebook,根据用户需求设置 kernel
规划章节:将内容大纲拆分为 3-5 段,每段对应一个逻辑章节
逐段生成:
make_section() 或 make_markdown_cell() + make_code_cell() 构造当前段的 cellappend_cells() 追加到 notebooksave_notebook() 保存当前进度(支持中途预览)收尾:添加总结章节,最终保存
重要:以下每一段代码都应由 agent 通过 terminal 工具分段执行,不要一次性全部执行,也不要让用户手动执行。
第 1 次执行(通过 terminal 工具):初始化 + 标题段
python
import sys
sys.path.insert(0, "<技能脚本目录>")
from nb_helpers import *
nb = new_notebook(kernel_name="python3")
append_cells(nb, [
make_markdown_cell("# 🔥 我的教程\n---\n**目标**:xxx"),
make_code_cell("import torch\nprint(torch.__version__)"),
])
save_notebook(nb, "my_tutorial.ipynb")
print(nb_info(nb)) # 验证状态
第 2 次执行(通过 terminal 工具):追加第一章
python
import sys
sys.path.insert(0, "<技能脚本目录>")
from nb_helpers import *
nb = load_notebook("my_tutorial.ipynb")
append_cells(nb, make_section(
"1.1 基础概念",
"这里是 Markdown 正文...",
["# 实验代码\nprint('hello')"],
))
save_notebook(nb, "my_tutorial.ipynb")
print(nb_info(nb)) # 验证状态
第 N 次执行(通过 terminal 工具):追加考核 + 总结
python
import sys
sys.path.insert(0, "<技能脚本目录>")
from nb_helpers import *
nb = load_notebook("my_tutorial.ipynb")
quiz = make_quiz_code_cell("q1", "1+1=?", 2, score=10)
append_cells(nb, [make_divider_cell(), make_markdown_cell("## 📝 考核时间"), quiz])
save_notebook(nb, "my_tutorial.ipynb")
print(nb_info(nb)) # 验证状态
save_notebook(),确保进度不丢失✅ 成功信息nb_info() 和 get_cell_summary() 检查当前状态import 和 load_notebook(),因为每次 terminal 调用是独立的 Python 进程加载: load_notebook(path) 加载现有 notebook
定位:
get_cell_summary() 查看所有 cell 概览find_cells_by_keyword() 按关键词搜索目标 cellfind_cells_by_id() 按 id 精确定位修改:
replace_cell() 替换整个 cellinsert_cells() 在指定位置插入新 celldelete_cells() 删除不需要的 cell保存: save_notebook(nb, path)
以下代码由 agent 通过 terminal 工具执行:
python
import sys
sys.path.insert(0, "<技能脚本目录>")
from nb_helpers import *
nb = load_notebook("existing.ipynb")
# 查看结构
for line in get_cell_summary(nb):
print(line)
# 在第 5 个 cell 后插入新内容
insert_cells(nb, 5, [
make_markdown_cell("### 补充说明\n新增的内容..."),
make_code_cell("# 新的实验代码"),
])
# 替换第 3 个 cell
replace_cell(nb, 3, make_markdown_cell("## 更新后的标题"))
save_notebook(nb, "existing.ipynb")
print(nb_info(nb)) # 验证修改结果
两种嵌入方式:
python
# 基础用法
img_md = embed_image_in_markdown("diagram.png", alt_text="架构图")
cell = make_markdown_cell(f"## 系统架构\n\n{img_md}\n\n上图展示了...")
# 指定宽度
img_md = embed_image_in_markdown("photo.jpg", alt_text="示例", width=500)
python
output = make_image_output("result.png")
cell = make_code_cell("# 运行结果如下", outputs=[output])
判题系统通过 SHA256 哈希存储答案,学生无法看到明文。
python
# 单选/填空题
quiz1 = make_quiz_code_cell(
question_id="q1",
question_prompt="torch.randn(3,4,5) 的 stride 是什么?",
answer=(20, 5, 1), # 正确答案(不会以明文出现在 notebook 中)
answer_type="tuple",
hints=["stride[i] = 后面所有维度 size 的乘积"],
score=10,
)
类型示例说明int``42整数float``3.14浮点数str``"hello"字符串(不区分大小写,去首尾空格)bool``True布尔值tuple``(20, 5, 1)元组list``[1, 2, 3]列表(会被转为 tuple 比较)
python
summary = make_quiz_summary_cell(
question_ids=["q1", "q2", "q3"],
total_score=30,
scores={"q1": 10, "q2": 10, "q3": 10}, # 可选,未指定时平均分配
)
append_cells(nb, [summary])
当用户要求生成完整教学 notebook 时,推荐结构:
text
1. 标题 + 目标描述 (markdown)
2. 环境准备 (code: import + version check)
3. --- 分隔线 ---
4. 章节 1: 概念讲解 (markdown) + 实验代码 (code) × N
5. --- 分隔线 ---
6. 章节 2: ...
7. --- 分隔线 ---
8. 📝 考核时间 (markdown)
9. 题目 1 (quiz code cell)
10. 题目 2 (quiz code cell)
11. ...
12. 📊 汇总评分 (quiz summary code cell)
13. --- 分隔线 ---
14. 完成总结 (markdown)
A: agent 只能通过工具调用的返回值感知执行结果。如果让用户在终端手动执行,即使输出了结果,agent 也无法检测到,会导致状态不一致。因此所有 Python 代码都必须通过 terminal 工具执行。
A: 会。每次 terminal 工具调用是一个独立的 Python 进程,上次定义的变量不会保留。因此每次执行都需要重新 import 并用 load_notebook() 加载之前保存的 notebook。
A: 每次追加前用 get_cell_summary() 检查当前状态,确认上一段已正确追加后再继续。
A: 用 find_cells_by_keyword() 定位目标 cell 的 index,然后用 replace_cell() 替换。
A: 哈希值虽然可见,但不可逆。学生无法从 16 位哈希推导出原始答案。如需更高安全性,可将判题逻辑放到外部服务。
A: 建议在嵌入前压缩图片(如 PNG → 降低分辨率、JPG → 降低质量),或只嵌入关键图片。
将多个 notebook 合并为一个,适合把分散的章节/讲义合并。
python
# 合并多个 notebook
merged = merge_notebooks(
paths=["chapter1.ipynb", "chapter2.ipynb", "chapter3.ipynb"],
output_path="full_course.ipynb",
add_dividers=True, # 在每个 notebook 之间添加分隔线
add_titles=False, # 不添加来源标注
)
将 notebook 的 Code cell 导出为 .py 文件,Markdown cell 可选地转为注释。
python
nb = load_notebook("tutorial.ipynb")
export_to_script(
nb,
output_path="tutorial.py",
include_markdown=True, # Markdown cell 作为注释写入
)
扫描 notebook 中所有 Markdown 标题,生成可点击跳转的目录 cell。
python
nb = load_notebook("long_tutorial.ipynb")
toc = make_toc_cell(nb, max_level=3, title="📑 目录")
insert_cells(nb, 0, [toc]) # 插入到 notebook 开头
save_notebook(nb, "long_tutorial.ipynb")
按指定索引顺序重排 cell,可用于调整结构。
python
nb = load_notebook("messy.ipynb")
for line in get_cell_summary(nb):
print(line)
# 按新顺序重排
reorder_cells(nb, [0, 3, 1, 2, 4])
save_notebook(nb, "messy.ipynb")
为 cell 打标签,然后按标签筛选。标签存储在 metadata.tags 中,JupyterLab 可直接识别。
python
# 打标签
cell = make_code_cell("x = 1")
tag_cell(cell, ["exercise", "easy"])
# 按标签查找
exercises = find_cells_by_tag(nb, "exercise")
for idx, cell in exercises:
print(f"Cell {idx}: {cell['cell_type']}")