Jupyter 在 AI 工程中的最佳实践——不只是实验,也可以做生产
Jupyter 在 AI 工程中的最佳实践——不只是实验,也可以做生产
适读人群:AI 工程师、数据工程师 | 阅读时长:约 13 分钟 | 核心价值:明确 Jupyter 的使用边界,以及如何让 Jupyter 的工作可以平滑迁移到生产代码
我见过一个同事把整个 RAG 系统跑在 Jupyter Notebook 里,直接对外提供服务。
他的逻辑是:Notebook 能跑就是能跑,为什么要重写?
这个系统后来出了几次事故:Cell 执行顺序搞混了导致结果不对,Kernel 崩了整个服务就挂了,没有日志也没有监控,出了问题不知道从哪里排查。
另一个极端:有的工程师一上来就拒绝用 Notebook,说 Notebook 不专业。结果做 Prompt 调试,每改一个参数都要改代码、重新部署、跑测试,效率极低。
Jupyter 的争议,根本上是工具和场景匹配的问题。这篇文章说清楚我的看法:Jupyter 适合什么、不适合什么,以及怎么在两个阶段之间平滑过渡。
Jupyter 真正适合的场景
一、数据探索和分析
在你还不知道数据长什么样的时候,Notebook 是最好的工具。你可以快速可视化、反复试验、实时看结果。这个阶段,代码的可复用性不重要,交互速度才重要。
# 探索向量数据库里的数据分布
import pandas as pd
import matplotlib.pyplot as plt
from collections import Counter
# 加载一批文档的 metadata
docs = vector_store.query(collection_name="knowledge", limit=1000)
df = pd.DataFrame([doc.metadata for doc in docs])
# 快速看数据分布
df['doc_type'].value_counts().plot(kind='bar', figsize=(10, 4))
plt.title('文档类型分布')
plt.tight_layout()
plt.show()
# 看 embedding 长度分布
df['text_length'] = df['text'].apply(len)
df['text_length'].hist(bins=50)
plt.title('文本长度分布')
plt.show()
print(df.describe())这种代码,如果放到正式项目里,你不知道它该放哪里,也不知道谁会复用它。放 Notebook 里刚好。
二、Prompt 调试和迭代
这是我用 Notebook 最多的场景。Prompt 调试本质上是一个高频迭代过程,你需要快速修改参数、立即看效果。
# Prompt 调试专用 Notebook 结构
# Cell 1:基础配置(只需要跑一次)
from openai import OpenAI
client = OpenAI()
MODEL = "gpt-4o-mini"
# Cell 2:测试用例定义(修改这里来换测试场景)
test_cases = [
{"input": "我的订单什么时候到?", "expected_intent": "order_tracking"},
{"input": "我要退款", "expected_intent": "refund"},
{"input": "产品质量有问题", "expected_intent": "complaint"},
]
# Cell 3:Prompt 定义(这是主要调试区域)
SYSTEM_PROMPT = """
你是一个客服意图识别系统。
给定用户消息,返回 JSON:{"intent": "...", "confidence": 0.0-1.0}
可能的 intent:order_tracking, refund, complaint, product_inquiry, other
"""
# Cell 4:运行测试(每次改完 Prompt 就重跑这个 Cell)
results = []
for case in test_cases:
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": case["input"]}
],
response_format={"type": "json_object"}
)
result = json.loads(response.choices[0].message.content)
results.append({
"input": case["input"],
"expected": case["expected_intent"],
"got": result["intent"],
"confidence": result["confidence"],
"correct": result["intent"] == case["expected_intent"]
})
# Cell 5:结果分析
df = pd.DataFrame(results)
accuracy = df['correct'].mean()
print(f"准确率: {accuracy:.1%}")
display(df[df['correct'] == False]) # 只看错误的这个调试流程,如果用正式的 Python 项目来做,每次改 Prompt 都要重跑整个脚本,而且没有直观的对比视图。Notebook 在这里有不可替代的优势。
三、一次性数据处理
批量跑一次数据迁移、批量重建索引、生成一批训练数据——这些任务做一次,不需要工程化:
# 批量重建向量索引(一次性任务)
import time
from tqdm import tqdm
documents = load_all_documents()
print(f"总共 {len(documents)} 篇文档需要重建索引")
failed = []
for i, doc in enumerate(tqdm(documents)):
try:
embedding = embedding_client.embed(doc.text)
vector_store.upsert(doc.id, embedding, doc.metadata)
except Exception as e:
failed.append((doc.id, str(e)))
# 避免 API 限流
if i % 100 == 0:
time.sleep(1)
print(f"完成。失败: {len(failed)} 篇")
if failed:
print("失败列表:", failed[:10])Jupyter 不适合的场景
一、长时间运行的服务
Kernel 崩了服务就挂。状态全在内存里,重启 Kernel 就没了。没有进程管理。一句话:Jupyter 不是服务器。
二、有复杂依赖关系的逻辑
当你的 Notebook 有 50 个 Cell,某些 Cell 之间有复杂的执行顺序依赖,而且有人经常乱序执行——这时候 Notebook 成了 Bug 温床。"请从头重新执行所有 Cell" 是每个 Notebook 维护者的噩梦。
三、需要版本控制和 Code Review 的核心逻辑
Notebook 文件是 JSON,diff 看起来一团乱麻。关键业务逻辑放在 Notebook 里,Code Review 基本是走形式。
四、需要单元测试的代码
你没法轻松地对 Notebook 里的函数写单元测试。如果一段代码需要测试,它就该在 .py 文件里。
边界在哪里:我的判断标准
我用三个问题来判断某段逻辑是留在 Notebook 还是迁移到正式代码:
问题一:这段代码会在生产环境被反复触发吗?
YES → 迁移到 .py 文件
问题二:其他人(或未来的我)需要维护这段代码吗?
YES → 迁移到 .py 文件,写测试
问题三:这段代码的正确性影响到用户吗?
YES → 必须迁移,必须测试按这个标准:
- Prompt 调试逻辑 → Notebook(实验完之后,把最终版 Prompt 提取出来放到配置文件)
- 数据探索脚本 → Notebook(一次性的,探索完就归档)
- Embedding 生成的核心逻辑 →
.py文件 - RAG 检索的核心逻辑 →
.py文件,有测试 - 批量数据迁移脚本 → Notebook(一次性跑完就归档,不需要维护)
如何让 Notebook 平滑迁移到生产代码
这是最实际的问题。你在 Notebook 里调试好的逻辑,怎么变成干净的生产代码?
方法一:边调试边提取函数
从一开始就养成这个习惯:在 Notebook 里写代码,但把可复用的部分立刻提取成函数。
# 在 Notebook 里
# 不好的写法(逻辑散在各处)
response = client.chat.completions.create(...)
text = response.choices[0].message.content
tokens = response.usage.total_tokens
# 好的写法(封装成函数,迁移成本极低)
def call_llm(prompt: str, system: str = "", model: str = "gpt-4o-mini") -> tuple[str, int]:
"""返回 (response_text, total_tokens)"""
messages = []
if system:
messages.append({"role": "system", "content": system})
messages.append({"role": "user", "content": prompt})
response = client.chat.completions.create(
model=model,
messages=messages
)
return response.choices[0].message.content, response.usage.total_tokens
# 调用
text, tokens = call_llm(user_prompt, SYSTEM_PROMPT)这个 call_llm 函数,直接复制到 .py 文件就能用,不需要任何修改。
方法二:Notebook 只做实验,配置和逻辑分离
把 Prompt 和参数提取到配置文件,Notebook 只负责测试,不存核心内容:
# config/prompts.py(正式文件,进 git)
INTENT_RECOGNITION_PROMPT = """
你是一个客服意图识别系统...
"""
# notebook/debug_intent.ipynb(实验文件)
from config.prompts import INTENT_RECOGNITION_PROMPT
# 调试的时候可以临时覆盖
test_prompt = INTENT_RECOGNITION_PROMPT + "\n\n额外约束:..."
# 测试通过后,把修改合并回 config/prompts.py方法三:Notebook 做验收测试
正式代码写好之后,用 Notebook 来做端到端的验收测试。这样 Notebook 变成了一种"可执行的测试文档":
# notebook/acceptance_test_rag.ipynb
"""
RAG 系统验收测试
运行所有 Cell 以验证 RAG 系统是否正常工作
最后更新:2025-01-15
测试通过版本:v2.3.1
"""
# 导入正式代码
from src.rag.retriever import RAGRetriever
from src.rag.generator import RAGGenerator
retriever = RAGRetriever.from_config("config/rag.yaml")
generator = RAGGenerator.from_config("config/rag.yaml")
# 测试一:基础检索
query = "企业年假政策是什么?"
docs = retriever.retrieve(query, top_k=3)
assert len(docs) > 0, "检索结果不能为空"
assert all(doc.score > 0.7 for doc in docs), "相关度分数应该大于 0.7"
print(f"检索测试通过,找到 {len(docs)} 条结果")
# 测试二:生成质量(人工判断)
answer = generator.generate(query, docs)
print(f"生成结果:\n{answer}")
print("\n[需要人工确认回答质量是否合理]")Notebook 的工程化规范
如果你的团队里确实需要维护 Notebook(比如周期性的数据报表),可以做一些最低限度的工程化:
规范一:每个 Notebook 有明确的执行顺序
在第一个 Cell 里写清楚:
# ============================================
# 文档名:RAG 索引质量分析
# 更新日期:2025-01-15
# 执行顺序:从上到下,不要乱序
# 依赖:需要先确保向量数据库已启动
# 预期执行时间:约 5 分钟
# ============================================规范二:用 nbstripout 清除输出
Notebook 的 output(尤其是图片和大量文本输出)会导致 git diff 很丑。用 nbstripout 在 commit 前自动清除:
pip install nbstripout
nbstripout --install # 安装 git hook,commit 时自动清除 output规范三:重要的 Notebook 配对一个 .py 文件
analysis/
├── explore_embeddings.ipynb # 探索用的 Notebook
├── explore_embeddings.py # 从 Notebook 提取出来的干净函数
└── test_embeddings.py # 单元测试总结
Jupyter 是探索工具,不是部署工具。它在实验、调试、可视化、一次性任务上无可替代;但它不适合承载需要稳定运行、可测试、可维护的生产逻辑。
这两件事不冲突。好的 AI 工程实践,是把 Jupyter 的灵活性和 Python 模块的工程性结合起来,不是非此即彼。
