简介:面向希望在 Jupyter Notebook 中系统掌握 DeepSeek 应用开发的读者,这份 PDF 从原理到实操提供了完整学习路径,适合数据从业者、算法工程师以及刚接触交互式 AI 开发的初学者。文档共 27 页,内容先梳理 DeepSeek 的技术架构与应用场景,再讲解 Jupyter Notebook 的安装、单元格操作、魔法命令、数据可视化等基础,随后聚焦两者集成时的环境准备、模型加载、调用测试和常见报错处理;后续章节则围绕交互式数据探索、模型开发与训练、性能优化与调试技巧展开,并给出文本分类、图像识别、时间序列预测、推荐系统等贴近实战的案例。资源为单个 1.83MB 的 PDF 文件,页面正文、图表和目录均完整清晰,既可通读建立知识体系,也可按章节快速定位所需内容。该资源已有 176 人学习,适合希望通过 Jupyter Notebook 提升 AI 开发效率的读者。
1. 从 Notebook 里直接对话 DeepSeek:为什么这件事值得搭
把 DeepSeek 放进 Jupyter Notebook,等于给数据分析流程装上一个能随时调用的“对话大脑”。你不需要切窗口、复制粘贴结果,也不用把大模型的输出当黑匣子——每一次调用、每一轮上下文、每一条 Prompt 变更都变成单元格里可回放、可修改、可重跑的实验记录。这个组合特别适合做三件事:用自然语言快速探查数据、批量评测不同 Prompt 的效果、把大模型输出直接喂给 pandas 做下一步处理。成本低、门槛低,但前提是得把接入方式和工程细节理顺。这篇笔记就按我自己常用的落地路径来拆:从 API 选型、对话器封装、Prompt 管理,到批量评测和常见翻车点,每一步都给可以直接抄的代码。
2. 接入方式与最小对话单元:先跑通再谈架构
2.1 选型:官方 API、本地部署还是第三方兼容层
DeepSeek 的接入通道主要有三条。第一条是官方 API,走 OpenAI 兼容协议,base_url 指向https://api.deepseek.com就能用,适合绝大多数场景,尤其是刚起步时。第二条是本地部署,用 vLLM 或 llama.cpp 把模型权重跑在自己机器上,适合数据敏感或需要反复微调 Prompt 的场景,但显存和运维成本得自己扛。第三条是第三方兼容层,比如某些云平台托管的 DeepSeek 镜像,接口格式一样,但“稳定性”和“价格”要额外甄别。
我的建议很直接:先走官方 API 把流程跑通,再根据瓶颈决定要不要切本地部署。原因很简单——Notebook 里的开发效率取决于迭代速度,官方 API 的接入成本最低,出问题也最好排查。等你要批量跑几百条评测、或者对延迟有硬性要求时,再上 vLLM 部署。如果你已经决定了本地部署,那也要在 Notebook 里保留一个统一的调用接口,这样切后端时只需要改base_url和api_key两个变量,代码主体不动。
2.2 最小调用单元:封装一个永远返回结构化结果的函数
先把环境准备好。官方 API 的 key 不要硬编码进 Notebook,用环境变量或者单独的配置文件管理,避免分享笔记时把密钥带出去。
import os from openai import OpenAI api_key = os.environ.get("DEEPSEEK_API_KEY", "") base_url = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com") client = OpenAI(api_key=api_key, base_url=base_url, timeout=60.0) def chat_once(messages, model="deepseek-chat", temperature=0.7, max_tokens=2048): try: resp = client.chat.completions.create( model=model, messages=messages, temperature=temperature, max_tokens=max_tokens, ) return { "ok": True, "content": resp.choices[0].message.content, "usage": resp.usage.model_dump(), # 记录 token 消耗 "error": None, } except Exception as e: # 把错误也结构化返回,方便在 Notebook 里继续处理而不是中断 return {"ok": False, "content": None, "usage": None, "error": str(e)} result = chat_once( [{"role": "user", "content": "用一句话解释什么是交互式 AI 开发"}] ) print(result["content"])这段代码有几个值得注意的点。timeout=60.0是必须显式设置的,默认值在长文本生成时很容易触发超时。返回值统一做成字典结构,ok字段用来判断成功与否——Notebook 里最常见的翻车方式就是异常直接抛出导致后续单元格全部失效,结构化返回可以让错误在数据层面被捕获,后面做批量评测时这个设计会省很多事。另外usage字段一定要拿到并落盘,它是后面算成本、调参的重要依据。
最后验证一下连通性,用一句话的请求确认 key、base_url、网络三个环节都通。
curl https://api.deepseek.com/v1/models \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -s | head -c 500如果这条命令能返回模型列表,说明网络和鉴权都正常。实际使用中我遇到过网络代理导致请求不可达的情况,curl 是最快定位手段。
3. 把静态对话变成可回放的实验流程:会话状态、Prompt 模板与参数面板
3.1 做一个会记事的对话器:用数据类保存上下文
chat_once只能做单轮问答,交互式开发的核心优势是“上下文连贯”。在 Notebook 里,最自然的做法是定义一个对话器类,把消息历史放在内存中,每次调用追加到上下文里。
from dataclasses import dataclass, field @dataclass class Session: system_prompt: str = "你是一个严谨的技术助手。回答要准确、简洁,有不确定的地方要明确指出。" history: list = field(default_factory=list) token_budget: int = 4000 # 粗略的上下文预算,防止无限膨胀 def start(self): self.history = [{"role": "system", "content": self.system_prompt}] return self def ask(self, user_msg: str, **kwargs) -> str: self.history.append({"role": "user", "content": user_msg}) # 简单启发式截断,先把最老的对话去掉,保留 system prompt while self._estimate_tokens(self.history) > self.token_budget and len(self.history) > 2: self.history.pop(1) # 永远不删下标 0 的 system prompt resp = chat_once(self.history, **kwargs) if resp["ok"]: self.history.append({"role": "assistant", "content": resp["content"]}) else: # 失败时不把错误当回答写进历史,否则下轮会污染上下文 raise RuntimeError(resp["error"]) return resp["content"] @staticmethod def _estimate_tokens(messages) -> int: # 粗略估计:中文约 1.5 token/字,英文约 1 token/词 total = 0 for m in messages: total += len(m["content"]) * 1.5 return int(total)这段代码的核心是pop(1)——它只在上下文超限时丢弃最早的对话轮次,永远不会丢弃系统提示词。日常使用中我发现两个问题:第一,不要过度依赖这个 token 估算,它只是防止上下文无限膨胀的保险丝,真要精确控制应该用模型自带的 tokenizer;第二,ask方法在请求失败时直接抛异常,而不是把错误信息写进history——如果你把 “请求失败,请重试” 这种内容写进历史,下一轮模型会真的以为这是你的指令,回答会变得很怪。这个坑我踩过,后面还会细说。
3.2 Prompt 模板化:同一个问题换不同人设,对比才有效
交互式 AI 开发里最常做的事就是“同一个问题,换 Prompt,看差异”。如果每次都手写整个 Prompt,对比时很难控制变量。我一般用string.Template或 f-string 做模板,再配合一个简单的参数面板。
from string import Template PROMPT_TEMPLATE = Template(""" 你是一名资深数据分析师。 请基于以下背景回答用户问题。 背景: $background 用户问题: $question 回答要求: - 先给出结论,再解释思路 - 如果信息不足,明确指出缺什么 """) def make_prompt(background: str, question: str) -> str: return PROMPT_TEMPLATE.substitute(background=background, question=question) # 典型的 Notebook 用法:把参数集中放在一个单元格,改完重跑 background = "我们有一个电商订单表,字段包括 order_id, user_id, amount, created_at" question = "如何识别刷单行为?请给出 SQL 思路" prompt = make_prompt(background, question) session = Session(system_prompt="你擅长 SQL 和数据分析") session.start() answer = session.ask(prompt, temperature=0.3, max_tokens=1024) print(answer)这里把system_prompt和prompt分开了。system_prompt定义模型的人格与回答边界,prompt定义当次任务的具体要求。对比实验时要改的是prompt,而system_prompt保持不变,这样结果差异才可归因。
参数temperature=0.3也是刻意设置的。做数据分析类任务时我会把温度调低来减少幻觉,做创意类任务时才调高到 0.8 以上。如果某次实验忘了记录温度,那结果几乎不可复现——同一段 Prompt 在不同温度下输出差异可能非常大。
4. 单轮到批量:在 Notebook 里搭一个可复现的评测管道
4.1 批量评测:从人工点按到数据集驱动
交互式开发做到一定阶段,你就会发现单条 Prompt 的问询不足以支撑决策。比如你改了系统提示词,想知道整体效果是变好还是变坏,得拿一二十条代表性问题上机器跑分。在 Notebook 里做批量评测,核心是把输入数据、模型配置、输出结果三者分离。
import pandas as pd import json import time # 评测集:每条包含 id、question、reference(可选标准答案) eval_set = [ {"id": 1, "question": "什么是窗口函数?", "reference": "用于跨行计算的 SQL 函数"}, {"id": 2, "question": "LEFT JOIN 和 INNER JOIN 的区别", "reference": "左连接保留左表全部行"}, # 实际场景中建议至少 20 条,覆盖不同难度 ] def run_evaluation(session_factory, eval_set, model="deepseek-chat", temperature=0.3): results = [] for item in eval_set: sess = session_factory() # 每个样本独立会话,避免互相污染 sess.start() try: answer = sess.ask(item["question"], model=model, temperature=temperature) results.append({ **item, "answer": answer, "latency": None, # 实际使用中可以用 time.time() 记录 "ok": True, }) except Exception as e: results.append({**item, "answer": None, "latency": None, "ok": False, "error": str(e)}) time.sleep(0.2) # 简单限流,避免触发 API 频率限制 return pd.DataFrame(results) df = run_evaluation(lambda: Session("你是 SQL 教学助手"), eval_set) df[["id", "question", "ok"]].head()这里的关键设计是session_factory而不是直接传一个 Session 实例。批量评测时每个样本都应该用全新的会话,否则前一个样本的对话历史会流进下一个样本,结果就失真了。lambda: Session(...)这个写法就是用来保证每次调用都创建全新对象。
time.sleep(0.2)是简单粗暴的限流。如果你用的是官方 API,并发太高会被限流甚至封 key;如果你用的是本地 vLLM 部署,并发高可能直接把显存打爆。更正规的做法是用asyncio+ 信号量做并发控制,但 Notebook 环境下我倾向于保守——跑得慢一点没关系,跑挂了才麻烦。
4.2 结果落盘与轻量评估:JSONL 是你的后悔药
批量跑完不能只看df.head()。我一般会把完整结果落成 JSONL 文件,每条记录包含原始输入、输出、token 消耗、耗时和错误信息。这样做的价值在于:任何一次评测结果都可以回溯,你可以对比“昨天改 Prompt 之前”和“今天改完之后”的输出差异,而不是靠记忆。
import json from pathlib import Path out_path = Path("./eval_results_jsonl/") out_path.mkdir(exist_ok=True) def save_results(df, tag="baseline"): filename = out_path / f"eval_{tag}_{int(time.time())}.jsonl" with open(filename, "w", encoding="utf-8") as f: for _, row in df.iterrows(): record = { "id": row["id"], "question": row["question"], "answer": row["answer"], "reference": row.get("reference", None), "ok": bool(row["ok"]), "tag": tag, "model": "deepseek-chat", "temperature": 0.3, } f.write(json.dumps(record, ensure_ascii=False) + "\n") print(f"已保存 {len(df)} 条结果到 {filename}") save_results(df, tag="v1_system_prompt")JSONL 比 CSV 更适合存这种半结构化数据,因为 answer 字段里可能包含换行、引号,CSV 处理起来麻烦,JSONL 天然规避了这个问题。tag字段用来标记这是哪一轮实验,建议用能表达语义的名字,比如v1_system_prompt表示“第一版系统提示词”,避免时间戳一多就分不清哪个是哪个。
至于评估方式,分两类。有标准答案的用代码自动算(比如 F1、BLEU、语义相似度);没有标准答案的,我习惯把结果导出成 Markdown 表格人肉看一遍——注意是人肉看,不是“看一眼”,是逐条打分。如果你对 DeepSeek 的输出有信心,也可以让它做裁判对结果打分,但要记住“大模型评大模型”有偏差,只能辅助不能替代。
5. 交互式 AI 开发的 5 个常见坑:从连不上到上下文污染
5.1 现象:第一个请求就超时,卡了几分钟没反应
原因:OpenAI客户端的默认超时时间在长文本生成场景下偏短,尤其是max_tokens设置较大时,服务端生成耗时容易超过客户端等待时间。另外,如果你所在网络需要通过代理访问外网,代理本身的不稳定也会放大超时概率。
解决:把timeout显式调大。我一般设为 60 秒,生成 2048 token 的常规回答足够。如果仍然超时,把max_tokens降下来,分段生成。代理问题用curl -v看连接过程,能快速定位是哪一跳卡住了。
5.2 现象:同一个 Prompt 跑三次,三次答案都不一样
原因:这是大模型的固有随机性,不是 bug。temperature越高,采样随机性越强;另外,模型推理时可能因为批次不同产生轻微差异。
解决:明确实验目的。做事实性问答时把temperature调到 0 或 0.1,接近贪婪解码,结果稳定得多。做创意生成时才调高温度。另外,DeepSeek 官方 API 支持seed参数(如果模型接受),设置固定seed能进一步降低差异,但注意seed不是绝对保证,不要依赖它做完全复现。
5.3 现象:多轮对话后,模型开始重复之前的错误结论
原因:上下文太长,早期的错误信息没有被正确管理。最常见的操作失误是:把一次失败的 API 返回结果当成正常内容写进了history,或者把调试用的临时信息(比如“这条是测试”)也传给模型当正经指令。
解决:严格区分“用户消息”和“系统内部消息”。调试信息永远不要进入history。用前面第 3 章的Session类,把失败分支单独处理,不要写进上下文。另外,定期清理历史——如果某个话题已经讨论完,就调用session.start()重开,别让旧话题干扰新话题。
5.4 现象:本地部署的模型,回答风格和 API 差异很大
原因:本地部署时,模型权重如果没有经过与官方 API 相同的对齐配置,行为会有差异。更常见的原因是system_prompt被覆盖了——比如你用 vLLM 部署时,--served-model-name参数设置不当,或者默认模板里没有正确传递 system 消息,导致模型根本看不到你的人设设定。
解决:部署时先做“裸奔测试”——不传 system_prompt,只传一条 user 消息,确认模型能正常回答。再传 system_prompt,对比风格是否变化。如果没变化,去检查部署框架的系统消息处理逻辑。不要一上来就怪模型,先确认消息传到了。
5.5 现象:批量跑 50 条数据,第 30 条断掉了,前面的全白费
原因:批量评测时没有断点续跑。网络抖动、API 限流、甚至是 Notebook 内核崩溃,都会导致任务中断。而df存在内存里,内核一重启就没了。
解决:分批执行 + 每批落盘。把 50 条拆成 5 个批次,每批跑完立刻写 JSONL。下次接着跑时,先读取已有的结果文件,跳过已完成的数据。下面的代码是这个思路的简化版:
def run_eval_with_resume(session_factory, eval_set, tag, batch_size=10): result_file = out_path / f"eval_{tag}.jsonl" done_ids = set() if result_file.exists(): for line in result_file.open(encoding="utf-8"): done_ids.add(json.loads(line)["id"]) pending = [e for e in eval_set if e["id"] not in done_ids] print(f"已完成 {len(done_ids)} 条,剩余 {len(pending)} 条") for i in range(0, len(pending), batch_size): batch = pending[i:i+batch_size] df_batch = run_evaluation(session_factory, batch) save_results(df_batch, tag=tag) # 追加模式,见下方说明注意:这个版本里save_results需要以追加模式打开文件,原先的“覆盖写”要改成mode="a"。这是最常见的血泪经验——第一次跑完 20 条,第二次接着跑直接清空重来。文件追加模式是断点续跑的命根子。
6. 收尾在验证上:给 Notebook 流程加一层“断言式检查”
整套流程跑通之后,最容易被忽略的是“怎么证明它还在正确地工作”。我的习惯是写一个验证单元格,放在每次批量实验之前,用三个断言快速把环境、鉴权和基本能力都检查一遍。这不是形式主义——交互式 AI 开发里 80% 的问题是在改代码过程中不小心碰坏了环境变量、覆盖了函数定义、或者改了某个全局变量。
def sanity_check(): # 1. 环境变量存在 assert os.environ.get("DEEPSEEK_API_KEY"), "缺少 DEEPSEEK_API_KEY" # 2. 客户端能连通 models = client.models.list() assert len(models.data) > 0, "API 鉴权失败或网络不通" # 3. 最小对话能完成,且返回结构正确 r = chat_once([{"role": "user", "content": "回复 OK 两个字母"}], max_tokens=10) assert r["ok"], f"对话失败: {r['error']}" assert "OK" in r["content"].upper(), "模型没有按预期回复" print("sanity check passed") sanity_check()这个检查函数放在 Notebook 最前面,每次重跑全流程之前执行一遍。如果断言挂了,说明环境变了,不用继续往下跑,省得浪费真金白银的 token。另一个习惯是每次实验开始前记录一下当前代码版本——Git 里打个 tag 或者把save_results的tag参数写得更有语义,这样排查问题时能准确回答“我是在哪个版本上跑出这个结果的”。
说到习惯,我自己的经验是:Notebook 里做 AI 开发,最大的敌人不是模型不够聪明,而是记录不够严谨。模型参数、Prompt 版本、评测集、结果文件——这四样东西哪怕有一项对不上,后面做的所有对比分析都是空中楼阁。希望这套流程能帮你少走一些弯路。
本文还有配套的精品资源,点击获取