☰
DeepSeek API 实战:从密钥到多轮对话的 Python 调用指南
2026/10/6 15:08:13 网站建设 项目流程

简介:这份资源面向具备一定编程基础、希望掌握AI模型API集成技术的开发者,系统讲解调用DeepSeek API的完整流程。内容从API工作机制入手,用通俗比喻帮助理解请求与响应的本质,再逐步展开注册账号、获取API Key、查阅文档、配置请求参数等准备工作,并以Python为例演示发送HTTP请求、解析服务器返回结果及处理常见错误的实操方法。文中还涉及批量处理、上下文管理、流式传输等提效技巧,以及密钥保护、数据隐私与信息安全方面的最佳实践,并给出实际项目的搭建思路。资源包为1个docx文档,约217KB,结构紧凑、便于通读。目前已有161人学习,适合想独立完成AI服务调用、将理论转化为可运行代码的读者参考。

1. 从外卖比喻到真实请求:DeepSeek API 到底能解决什么

很多人第一次接触 DeepSeek API,是被“外卖小哥”这个比喻带进来的——你下单,后台做披萨,API 负责把结果送到你手里。比喻本身没问题,但真正落到代码里,新手最容易卡住的地方恰恰是:不知道“下单”这个动作在 HTTP 层面到底长什么样。你打开官网、注册账号、拿到一串sk-开头的密钥,然后呢?请求发到哪个地址、Header 里放什么、Body 里messages数组的role有哪几种取值、返回的 JSON 里内容藏在第几层——这些才是决定你能不能跑通第一个请求的关键。

这篇笔记面向的是有基础 Python 能力、想把 DeepSeek 的对话能力接进自己脚本或小工具里的开发者。我会按“拿到密钥 → 发出第一个请求 → 处理返回 → 避坑 → 进阶”的顺序,把每一步的参数含义和失败排查讲清楚。你跟着走完,至少能得到一个能稳定跑通的调用模板,而不是停留在“知道有 API 这回事”。

2. 准备工作与第一个可运行请求:密钥、地址、Header 和 Body 怎么配

2.1 注册、创建 API Key 与文档里真正要看的三个字段

注册流程本身不复杂:进官网、填邮箱或手机号、验证。真正要留意的是创建 API Key 这一步。在后台的 API 管理页面点“生成新密钥”,你会得到一串类似sk-12ab34cd56ef78gh90ij12kl34mn56op的字符。这串东西只在生成时完整显示一次,关掉页面就再也看不到全文了,所以生成后立刻复制到安全的地方,这是血泪经验。

拿到密钥后,别急着写代码,先花五分钟把官方文档里这三个字段确认清楚:

字段作用常见取值/位置
请求地址请求发往哪个 URLhttps://api.deepseek.com/v1/chat/completions
认证方式证明你有权限Header 里的Authorization: Bearer <你的密钥>
模型名用哪个模型deepseek-chat等,以文档当前说明为准

文档里还会列出一堆参数,但第一次调用你只需要关心四个:model、messages、temperature、max_tokens。其余的top_p、frequency_penalty之类,等跑通之后再回来调。

提示:请求地址和模型名会随平台更新变化,写代码前以官方文档当前页面为准,不要照抄网上半年前的教程。

2.2 用 requests 发出第一个请求:五步拆解

Python 里最直接的调用方式就是用requests库。先装依赖:

pip install requests

然后按下面五步走。我把每一步单独拆开,方便你对照排查。

第一步,设置请求头。Header 负责告诉服务器“我是谁”和“我发的是什么格式”:

import requests headers = { "Authorization": "Bearer YOUR_API_KEY", # 把 YOUR_API_KEY 换成真实密钥 "Content-Type": "application/json" # 声明请求体是 JSON 格式 }

Authorization的值必须是Bearer加一个空格再加密钥,这个空格漏掉就是 401 错误的头号原因。Content-Type固定写application/json,因为下面json=参数会自动序列化并带上这个头,但显式写出来更保险。

第二步,准备请求体。请求体是真正的“订单内容”:

data = { "model": "deepseek-chat", # 模型名,以文档为准 "messages": [ # 对话消息列表 {"role": "user", "content": "你好!请用鲁迅的风格写一段关于秋天的散文"} ], "temperature": 0.7, # 0 偏保守,1 偏发散 "max_tokens": 500 # 限制回复最大长度 }

messages是一个数组,每个元素有role和content两个键。role常见取值是user(你发的)、assistant(模型回的)、system(设定人设或规则)。第一次调用只放一条user消息就够了。temperature控制随机性,写代码、做抽取任务时调到 0.2 以下更稳;写文案、头脑风暴可以放到 0.8 以上。max_tokens是回复长度的硬上限,设太小会导致回答被截断。

第三步,发送 POST 请求。用requests.post把地址、头、体拼起来:

response = requests.post( "https://api.deepseek.com/v1/chat/completions", headers=headers, json=data )

注意这里用的是json=data而不是data=data。json=会自动把字典序列化成 JSON 字符串并设置正确的 Content-Type;用data=传字典则不会序列化,服务器收到的是 Python 的字典字符串表示,直接报 400。

第四步,判断状态码并取内容。服务器返回的 JSON 里,模型输出藏在choices[0].message.content:

if response.status_code == 200: result = response.json() print(result["choices"][0]["message"]["content"]) else: print(f"请求失败,状态码:{response.status_code}") print(response.text) # 打印完整错误信息,方便定位

response.json()把返回的 JSON 字符串转成 Python 字典。choices是一个数组,通常只有一个元素,取[0]即可。如果状态码不是 200,response.text里往往有具体的错误描述,比如Invalid API key或Model not found,先看这个再改代码。

第五步,把密钥从代码里挪出去。上面代码里直接写密钥只是演示,实际项目里必须用环境变量:

import os api_key = os.environ.get("DEEPSEEK_API_KEY") headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }

在终端里用export DEEPSEEK_API_KEY="sk-..."设置(Windows 用set),代码里只读不写。这样即使代码被分享出去,密钥也不会跟着泄露。

2.3 返回结构长什么样:一次请求的完整数据流

跑通之后,你拿到的response.json()大致是这样一个结构:

{ "id": "chatcmpl-xxxx", "object": "chat.completion", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "秋天,总是来得悄无声息……" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 28, "completion_tokens": 156, "total_tokens": 184 } }

finish_reason值得关注:值是stop表示正常结束,是length表示被max_tokens截断了,这时候你就该把max_tokens调大。usage里的 token 数直接关系到计费,养成每次调用后看一眼的习惯,能帮你估算成本。

3. 多轮对话与流式输出:让请求更像真实聊天

3.1 用 messages 数组维护上下文

单次问答很简单,但真实场景往往需要多轮。DeepSeek 的 API 本身不保存会话状态,上下文完全靠你在每次请求时把历史消息一起传进去。做法是维护一个列表,每轮把用户消息和模型回复都追加进去:

conversation = [ {"role": "system", "content": "你是一个简洁的技术助手,回答不超过三句话。"}, {"role": "user", "content": "什么是 API?"} ] # 第一次请求 response = requests.post(url, headers=headers, json={ "model": "deepseek-chat", "messages": conversation, "temperature": 0.5 }) reply = response.json()["choices"][0]["message"]["content"] conversation.append({"role": "assistant", "content": reply}) # 第二次请求,带上完整历史 conversation.append({"role": "user", "content": "能举个例子吗?"}) response = requests.post(url, headers=headers, json={ "model": "deepseek-chat", "messages": conversation, "temperature": 0.5 }) print(response.json()["choices"][0]["message"]["content"])

这里的关键点是:conversation列表在两次请求之间没有被清空,第二次请求把第一轮的user和assistant消息都带上了,模型才能“记得”之前聊了什么。system消息放在最前面,用来设定行为边界,比如限制回答长度、指定语气。

注意:上下文越长,消耗的 token 越多,费用也越高。长对话要定期裁剪,比如只保留最近 10 轮,或者把早期内容做摘要后再传入。

3.2 流式输出:让回复一个字一个字蹦出来

默认情况下,API 会等模型生成完整回复后一次性返回,长回答可能要等好几秒。开启stream后,服务器会分块推送,你可以边收边打印:

data = { "model": "deepseek-chat", "messages": [{"role": "user", "content": "写一段 200 字的科幻开头"}], "stream": True } response = requests.post(url, headers=headers, json=data, stream=True) for line in response.iter_lines(): if line: decoded = line.decode("utf-8") if decoded.startswith("data: "): payload = decoded[6:] # 去掉 "data: " 前缀 if payload.strip() == "[DONE]": break import json chunk = json.loads(payload) delta = chunk["choices"][0].get("delta", {}) if "content" in delta: print(delta["content"], end="", flush=True)

几个参数要留意:stream=True在requests.post里也要同步设置,否则iter_lines不会逐行返回。返回的每一行以data:开头,最后一行是data: [DONE]。delta里可能没有content键(比如第一个 chunk 只带role),所以用.get("content")做判断。流式模式下usage字段通常不会出现在每个 chunk 里,需要额外处理。

3.3 超时、重试与并发的基本处理

网络请求不可能永远成功。生产环境里至少要加超时和重试:

from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503]) session.mount("https://", HTTPAdapter(max_retries=retry)) response = session.post(url, headers=headers, json=data, timeout=30)

timeout=30表示 30 秒没响应就抛异常,避免程序卡死。Retry配置了遇到 429(限流)和 5xx(服务器错误)时自动重试 3 次,backoff_factor=1让每次重试间隔递增。这套组合能挡掉大部分偶发网络抖动,但 401 和 400 这类客户端错误不会重试,因为重试也没用,得改代码。

4. 避坑与排查:401、400、截断和超时的真实原因

4.1 401 认证失败:密钥和 Bearer 前缀

现象:返回{"error": "Invalid API key"}或状态码 401。

原因:最常见的是三个——密钥复制不完整(首尾漏字符)、Bearer和密钥之间少了空格、密钥已被删除或过期。

解决:把 Header 里的Authorization值打印出来,逐字符核对。正确格式是Bearer sk-xxxx,Bearer后面有且只有一个空格。如果确认格式没问题,去后台重新生成一个密钥替换。

4.2 400 请求格式错误:json= 和 data= 的区别

现象:返回 400,错误信息里提到invalid request body或expected JSON。

原因:用了data=data而不是json=data,导致字典没有被序列化成 JSON;或者messages里缺少role/content键;或者model名写错了。

解决:统一用json=参数。检查messages数组里每个元素是否都有role和content。model的值从文档里复制,不要手打。

4.3 回复被截断:max_tokens 和 finish_reason

现象:回答到一半突然停了,句子不完整。

原因:max_tokens设得太小,模型还没说完就触发了长度上限。返回的finish_reason会是length而不是stop。

解决:先看finish_reason,如果是length,把max_tokens调大。但要注意模型本身有上下文窗口上限,输入加输出的总 token 数不能超过这个值,超了会直接报错。

4.4 响应慢或超时:流式与超时参数

现象:请求发出后十几秒没反应,最后抛ReadTimeout。

原因:长回答在非流式模式下需要等全部生成完才返回;网络链路不稳定;服务器端负载高。

解决:对长文本场景开启stream=True,边生成边接收,体感快很多。同时设置合理的timeout,比如(10, 60)表示连接超时 10 秒、读取超时 60 秒。如果频繁超时,检查本地网络,或者把请求放到异步任务里跑。

4.5 密钥泄露与用量失控

现象:收到账单发现用量远超预期,或者密钥出现在公开仓库里。

原因:密钥硬编码在代码里被提交到了 Git;或者没有设置用量上限。

解决:密钥只从环境变量读取,.env文件加入.gitignore。在后台设置每日或每月消费限额,开启用量告警。定期轮换密钥,旧密钥保留一周过渡后删除。

5. 进阶:用 Flask 搭一个带上下文管理的写作助手

把前面所有东西串起来,做一个最小可用的 Web 应用:用户输入主题,后端调用 DeepSeek 生成文章,并且支持连续追问修改。技术栈是 Python + Flask。

先装依赖:

pip install flask requests

核心代码:

import os import json import requests from flask import Flask, request, jsonify, render_template app = Flask(__name__) API_URL = "https://api.deepseek.com/v1/chat/completions" HEADERS = { "Authorization": f"Bearer {os.environ.get('DEEPSEEK_API_KEY')}", "Content-Type": "application/json" } # 用字典按 session_id 保存每个用户的对话历史 sessions = {} def call_deepseek(messages, temperature=0.5, max_tokens=1500): """封装一次 API 调用,返回文本内容或错误信息""" payload = { "model": "deepseek-chat", "messages": messages, "temperature": temperature, "max_tokens": max_tokens } try: resp = requests.post(API_URL, headers=HEADERS, json=payload, timeout=(10, 60)) if resp.status_code == 200: return resp.json()["choices"][0]["message"]["content"], None return None, f"API 错误 {resp.status_code}: {resp.text}" except requests.exceptions.Timeout: return None, "请求超时,请稍后重试" @app.route("/") def index(): return render_template("index.html") @app.route("/generate", methods=["POST"]) def generate(): data = request.get_json() topic = data.get("topic", "").strip() session_id = data.get("session_id", "default") if not topic: return jsonify({"error": "主题不能为空"}), 400 # 取出或初始化该会话的历史 history = sessions.setdefault(session_id, [ {"role": "system", "content": "你是一个专业写作助手,输出结构清晰的中文文章。"} ]) history.append({"role": "user", "content": f"请围绕「{topic}」写一篇 800 字左右的文章,分三个小节。"}) content, error = call_deepseek(history) if error: return jsonify({"error": error}), 500 history.append({"role": "assistant", "content": content}) # 只保留最近 20 条消息,防止上下文无限增长 sessions[session_id] = history[-20:] return jsonify({"article": content}) if __name__ == "__main__": app.run(debug=True, port=5000)

这段代码有几个设计点值得说。sessions字典用session_id做键,每个用户或每个浏览器标签页可以有独立的历史,互不干扰。call_deepseek把请求逻辑封装成一个函数,超时设成(10, 60),连接 10 秒、读取 60 秒,兼顾响应速度和长文本生成。历史记录用history[-20:]裁剪,只保留最近 20 条消息,避免 token 无限累积导致费用失控或超出上下文窗口。

前端index.html只需要一个输入框和一个展示区域,用fetch调/generate即可。如果你想支持“继续修改”,再加一个输入框,把用户的新要求追加到history里再调一次call_deepseek,模型就能基于上一版文章做修改。

验证方法很简单:启动 Flask 后,在浏览器里输入一个主题,看是否返回文章;然后追问“把第二段改得更口语化”,看返回的内容是否基于上一版修改。如果第二次请求报错,检查history是否被正确追加和裁剪。

从那以后我每次接一个新的 API,都会先把密钥塞进环境变量、写一个最小请求脚本跑通、再打印一次完整返回结构确认字段位置,最后才动业务代码。这套习惯帮我省掉了大量“明明代码一样却跑不通”的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询