简介:DeepSeek API 调用指南由 1 个 docx 文档组成,整体仅 13KB,适合具有一定编程经验的开发者或研究人员,希望在聊天机器人、客服系统、智能助理等场景中快速集成 DeepSeek 的语言处理能力。文档从官网注册与 API Key 获取讲起,逐步说明 Python 环境准备、OpenAI SDK 和 HTTP 请求两种调用方式,并给出可复用的代码示例。在请求与响应处理部分,还介绍了 JSON 数据解析、常见错误应对,以及 temperature、max_tokens 等生成参数调整方法;同时专门演示了通过 stream=True 实现流式输出,以及维护会话上下文支持多轮对话。文末提醒使用者保护密钥并遵守接口使用规范,附有官方文档入口,便于继续深入查阅。整体内容紧凑、步骤清晰,章节安排贴近实际调用流程,重点覆盖从环境搭建到集成调优的关键环节,可作为后续业务集成的起步参考。目前已有 3156 人学习下载。
1. DeepSeek API 调用指南:为什么流式输出才是真正的分水岭
很多人第一次接触 DeepSeek API 时,最容易被卡住的不是注册,而是对"流式输出"的误判:以为只是把 stream 参数改成 true 那么简单,结果一跑起来就遇到响应迟迟不返回、SSE 数据解析不出来、长文本莫名截断。DeepSeek API 调用指南这个主题,真正要解决的其实是三件事——拿到一个能用的密钥、构造出合规的 messages 请求体、以及理解流式响应里那一条条data:开头的增量数据。这篇文章把这些环节从头到尾拆开讲,新手可以照着命令一步步跑通,熟手可以直接跳到参数边界和避坑清单来对照自己的调用姿势。
2. 注册与密钥:从创建账号到安全存放 API Key 的完整操作
2.1 注册环节的四个必填项与实名认证边界
DeepSeek API 的接入入口是开放平台控制台,注册流程本身不复杂,但很多人会在认证环节犹豫。常见做法是用手机号或邮箱注册,然后完成邮箱/短信验证,设置登录密码,再进入控制台。这里有一个容易被忽略的点:注册完成后你还不一定能创建 API Key,需要先完成实名认证。个人开发者和企业开发者的认证路径不同,个人认证通常只需要基础身份信息,企业认证会额外要求营业执照之类的材料。我的建议是,如果你只是做个人工具、Demo 或者学习项目,先走个人认证就够了,不用急着上企业认证。认证类型直接影响的是配额上限,而不是能不能调用。
还有一个边界要提前说清楚:认证完成不等于账户有余额。DeepSeek API 是付费调用的,控制台会提供充值入口,新账户通常默认没有可用额度。第一次跑通前,最好先充一小笔钱,免得在排查问题时分不清是代码写错还是账户欠费。不同平台的充值和计费策略略有差异,以控制台右上角的账户信息为准。注册这一步的核心目标只有一个——让控制台上能看到一个可用的账户 ID 和一个能创建密钥的入口。
2.2 创建 API Key 的正确姿势:最小权限与环境变量存放
注册完成并通过认证后,进入控制台的 API Keys 页面,创建一个新密钥。创建时会给密钥起一个名字,我一般习惯按项目粒度起名,比如project-x-key,原因后面会讲。创建成功后,平台会完整显示一次密钥字符串,这个字符串不会再次出现,必须当场复制保存。密钥通常以sk-开头,后面跟一串随机字符,它就是后面每次请求的"身份证"。
密钥的存放是第一个容易翻车的地方。我见过有人把密钥直接写在 Python 文件里,甚至提交到 Git 仓库,结果被扫描机器人拉取后拿去刷额度。正确的做法是让密钥只存在于本地环境变量或独立的配置文件中。两种常见方案:
# 方案A:写入 ~/.bashrc 或 ~/.zshrc,然后 source ~/.bashrc export DEEPSEEK_API_KEY="sk-你的密钥"# 方案B:项目根目录放 .env 文件,配合 python-dotenv 读取 # .env 内容:DEEPSEEK_API_KEY=sk-你的密钥 from dotenv import load_dotenv import os load_dotenv() api_key = os.getenv("DEEPSEEK_API_KEY")方案A适合个人开发机,每次打开终端自动生效;方案B适合项目工程化,.env文件要加入.gitignore,避免被提交到仓库。这里有个很多人踩过的坑:修改了.bashrc后,已经在运行的终端不会自动读到新变量,必须重新source或重开终端。而.env方案里,load_dotenv()只读取文件内容,不会覆盖系统已有同名环境变量,调试时如果发现 key 不对,先确认是不是读了旧值。
2.3 密钥轮换与吊销:让 key 只出现在该出现的地方
密钥不是创建完就一劳永逸的。当怀疑密钥泄露、某个项目下线、或者协作者离职时,应该回到控制台吊销旧密钥并创建新密钥。吊销是即时生效的,旧密钥从吊销那一刻起就无法通过认证。这就是我强调按项目粒度创建密钥的原因:key 之间互相独立,吊销一个不影响其他项目的调用。如果所有项目共用一把 key,一旦需要轮换,所有服务都会因为认证失败而同步不可用。
轮换时有一个顺序问题需要留意:先在控制台创建新密钥,再更新代码或环境变量里的旧值,最后才吊销旧密钥。反过来操作的话,中间会有一段窗口期服务处于认证失败状态。更新完环境变量后,要像前面说的那样重新加载配置,有经验的开发者通常会在轮换后立即用一个最小请求验证新 key 是否生效,而不是直接切流量。这个验证请求在后面 curl 那一节会给出。
3. 第一次调用:用 Python 把 chat/completions 请求跑通
3.1 依赖选择与最小请求体:messages 是唯一的输入约定
调用 DeepSeek API 有两种主流方式:直接用requests库发 HTTP 请求,或者用兼容 OpenAI 的官方 SDK。第一次做的时候,我建议先用requests,它能让你看到完整的 HTTP 交互过程,出问题时排查起来更直观。SDK 封装度高,但遇到超时、流式解析这类问题时,反而多了一层黑匣子。
一个完整的聊天补全请求,核心就是 POST 到聊天补全接口,请求体里的关键字段只有四个:model、messages、stream、temperature。其中最难理解的是messages,它是一个消息列表,每条消息必须有role和content两个字段。role只有三种取值:system设定助手人设和行为边界,user表示用户输入,assistant表示模型的回复历史。多轮对话就是把历史消息全部按顺序塞进这个列表。最小可用代码如下:
import requests import os api_key = os.getenv("DEEPSEEK_API_KEY") BASE_URL = "https://api.deepseek.com" # 以平台文档为准 MODEL_NAME = "deepseek-chat" # 换成控制台可见的模型标识 payload = { "model": MODEL_NAME, "messages": [ {"role": "system", "content": "你是一个简洁的技术助手"}, {"role": "user", "content": "用一句话解释什么是 API"}, ], "stream": False, "temperature": 0.7, } resp = requests.post( f"{BASE_URL}/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, json=payload, timeout=(5, 60), ) print(resp.status_code) print(resp.json()["choices"][0]["message"]["content"])这段代码里,BASE_URL到底带不带/v1前缀,不同平台风格不同,第一次请求前先在文档里确认。timeout=(5, 60)是双值元组,前一个是连接超时,后一个是读取超时;如果你的问题比较简单,5 秒连接 + 60 秒读取足够。temperature控制随机性,值越大回答越发散,一般问答场景固定 0.7 即可。stream先保持False,把流程整体跑通后再升级成流式。
3.2 用 curl 验证连通性:绕过 SDK 直接看 HTTP 层
在写任何代码之前,我强烈建议先用 curl 做一次连通性验证。这一步能把问题快速分成两类:一类是你的网络和密钥有问题,另一类是代码逻辑有问题。curl 是最底层的验证工具,不经过任何语言环境,看到的响应就是服务器最真实的样子。
curl -X POST "$BASE_URL/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -d '{ "model": "deepseek-chat", "messages": [{"role": "user", "content": "你好"}], "stream": false }'如果一切正常,你会看到返回的 JSON 里包含choices、usage和id字段。如果返回 401,检查环境变量是否真的被终端读到了,在命令行里执行echo $DEEPSEEK_API_KEY看看输出有没有值。如果返回 400,大概率是messages结构不对,比如缺了role。如果返回 402,那就是账户余额不足。curl 验证通过后,再回到 Python 侧继续,你会发现自己写代码的出错点立刻缩小了很多。
3.3 解析响应:choices、usage 与 finish_reason 怎么配合读
非流式响应是一个标准 JSON 结构,解析时只需要关注三个字段,这里用一张表说明它们各自的用途:
| 字段 | 类型 | 用途 |
|---|---|---|
choices[0].message.content | string | 模型生成的完整文本,是业务上最常用的字段 |
choices[0].finish_reason | string | 结束原因:stop表示正常结束,length表示触达 token 上限被截断 |
usage | object | 计费信息,包含prompt_tokens、completion_tokens、total_tokens |
很多新人在拿到响应后只取content,忽略了finish_reason,这会导致一个问题:当回答刚好被max_tokens截断时,看起来是"模型话说了一半",但实际上响应是完整的,只是被截断了。判断方法就是看finish_reason是不是length。usage则用于成本核算,每次调用后把它记到日志里,月底对账就不用靠猜。还有一个细节:choices是个数组,单次请求默认只有一个元素,但代码里最好按数组方式访问,因为你永远不知道后续会不会支持多候选输出。
4. 流式输出:把 stream=true 讲透,从 SSE 协议到逐词渲染
4.1 为什么要流式:首 token 延迟是体感的分水岭
非流式请求要等模型把整个回复全部生成完才一次性返回,这个时间在长文本场景下可能达到几十秒。用户在这几十秒里看到的是一个静止的加载图标,体验上很像"卡死了"。流式输出的价值在于,模型每生成一小段内容就立刻推送给你,用户看到的第一个字通常在一两秒内就会出现。这个首 token 延迟,才是大模型应用体验的分水岭。
从工程角度,流式不只是体验优化,还带来了两个实际好处:一是用户可以提前判断回答方向,如果不对可以立即中断,省掉后续 token 的费用;二是服务端不会因为长连接而积累大量未响应请求,客户端可以边收边渲染,内存占用更低。坏处是调用流程变复杂,你需要处理一个持续推送的 HTTP 响应,而不是简单地读一次 JSON。这个复杂度是真实存在的,但它是值得的,所有面向用户对话的接入方案最终都会切到流式。
4.2 用 requests 手写 SSE 解析:最小编码实现
流式模式下,响应体不再是单一 JSON,而是采用 SSE(Server-Sent Events)协议,每行以data:开头携带一个 JSON 片段。解析的思路不是一次性读完整响应,而是把响应当作一个持续的数据流,逐行读取。最小实现如下:
import json import requests def stream_chat(api_key, base_url, model, messages, temperature=0.7): resp = requests.post( f"{base_url}/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, json={ "model": model, "messages": messages, "stream": True, "temperature": temperature, }, stream=True, # 关键:让 requests 不一次性下载全部内容 timeout=(5, 120), # 读取超时放大,长回答需要更长的读取窗口 ) resp.raise_for_status() for raw_line in resp.iter_lines(decode_unicode=True): if not raw_line or not raw_line.startswith("data:"): continue data = raw_line[5:].strip() if data == "[DONE]": break payload = json.loads(data) delta = payload["choices"][0]["delta"] if delta.get("content"): yield delta["content"]这段代码有四个关键点需要理解。第一,stream=True是 requests 库的行为开关,没有它响应体会被完整缓存在内存里,流式就失去意义。第二,iter_lines(decode_unicode=True)按行迭代,SSE 协议里的空行是心跳分隔符,要直接跳过,否则json.loads会解析失败。第三,data:前缀的处理要注意空格,有些实现是data:后直接跟 JSON,这里用[5:]截断后strip()可以兼容两种风格。第四,结束标记[DONE]不能忘记判断,漏掉它会导致 for 循环一直等在读取上直到超时。
每一行data:的 JSON 里,核心字段是choices[0].delta.content。delta是增量,不是完整文本,所以你不能直接把它当作整个回答。只有delta.content有值时才是新生成的字,最后一个 chunk 可能只包含finish_reason而没有content。调用方拿到这个生成器后,每收到一个增量就渲染一段,用户看到的效果就是"逐字输出"。
4.3 什么时候该用 SDK、什么时候该自己解析
如果你的场景只是简单地把流式文本显示出来,不关心中间过程,直接用兼容 OpenAI 的 SDK 是最省力的选择:
from openai import OpenAI import os client = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY"), base_url=BASE_URL, ) stream = client.chat.completions.create( model=MODEL_NAME, messages=[{"role": "user", "content": "讲个冷笑话"}], stream=True, ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True)SDK 把 SSE 解析细节全部封装好了,迭代器直接吐出结构化的 chunk,代码量少一半。我判断的标准很简单:如果你不需要对增量内容做任何加工,就选 SDK;如果你要做敏感词过滤、增量计数、调试日志记录、或者想完全掌控协议层面的行为,就自己解析 SSE。还有一个中间态:先用 SDK 跑通,等遇到排查不动的诡异问题时,再换成手写解析观察原始数据。这两种方式不是互斥的,而是排障时的两条腿。
5. DeepSeek API 调用避坑:认证、超时、限流与计费的 5 条实战记录
5.1 401 还是 402:密钥失效与余额不足的区分方法
现象:调用时返回 HTTP 401 或 402,日志里只记录了状态码,看不出具体原因,于是开始怀疑代码写错了,反复检查 headers 和请求体。
原因:401 是认证失败,说明请求携带的密钥不存在、已吊销或格式错误;402 是账户余额或额度不足,密钥本身是有效的。这两个状态码指向完全不同的处理路径,混在一起排查会浪费大量时间。
解决:先看响应体里的error字段,它通常会给出具体的 message。401 的处理路径是去控制台检查密钥是否存在、是否已被吊销,或者创建新密钥再试;402 的处理路径是去账户中心充值或检查配额。还有一个小技巧:用同一个密钥分别请求一次简单的聊天接口和一个不存在的路径,观察 401/404/200 的差异,能快速判断是网络层问题还是服务端认证问题。
5.2 连接超时与读取超时:流式请求为什么必须单独设 read timeout
现象:短问答一切正常,一旦用户提问需要生成长文,requests就抛ReadTimeout异常,但连接阶段从没超时过。
原因:很多人在代码里只设置了timeout=5,这个值会同时作用于连接和读取两个阶段。普通请求在 5 秒内能读完响应,但流式长文生成可能持续几十秒,读取超时被误触发了。
解决:使用双值元组timeout=(5, 120),前值是连接超时,后值是读取超时。流式场景里读取超时设 120 秒甚至更长都不为过,因为读取超时的意义是"相邻两次数据到达之间的最大间隔",不是"整个请求的总时长"。长回答虽然总耗时长,但只要服务器在持续推送数据,读取超时就不会触发。
5.3 并发与限流:429 背后的退避策略
现象:并发量稍微一上来,就开始收到 429 状态码,而且越重试越频繁。
原因:API 对请求频率和 token 消耗速率都有配额限制,超过阈值会返回 429。很多人遇到 429 的第一反应是立即重试,结果因为请求仍然超限,形成雪崩式重试。
解决:采用指数退避策略,第一次等待 1 秒,第二次 2 秒,第三次 4 秒,最大重试次数控制在 5 次以内。退避时间要带随机抖动,避免多个客户端在同一时刻集体重试。如果退避重试后仍然 429,说明你的并发确实超出了账户配额,这时该做的是降低并发数,而不是继续加退避时间。还可以对照 5.1,区分 429 是限流还是余额耗尽触发的保护机制。
5.4 max_tokens 默认值陷阱:长文输出被截断
现象:模型回答到一半戛然而止,没有报错,内容明显没说完,但响应状态是成功的。
原因:max_tokens限制了单次响应的最大长度,默认值通常偏低。当回答长度触顶时,finish_reason会变成length,但很多调用方只取了content,完全没看这个字段。
解决:显式设置max_tokens,常见做法是设到 2048 或 4096,具体值按业务需求来。同时,在代码里判断finish_reason,如果是length,可以在界面上提示"回答被截断",或者在逻辑上追加一次后续请求让模型继续。这里还有一个省钱技巧:不要在system提示词里写"请详细回答"这类空泛要求,那只会白白消耗输出 token,真正需要长文时用max_tokens精确控制。
5.5 上下文撑爆:多轮对话 400 报错的隐藏原因
现象:单轮调用正常,多轮对话进行到十几轮后,突然开始返回 400,响应体里提示上下文相关错误。
原因:messages列表把每轮的消息全部保留,Token 总量在累积增长,最终超出模型的上下文窗口。很多新人对上下文窗口没有概念,以为聊天记录越多越好。
解决:实现一个滑动窗口,只保留最近的 N 轮消息,system提示词始终放在列表最前面不要参与裁剪。更精细的做法是在代码里做 token 估算,当总量超过阈值时,把最早的历史消息合并成一条摘要。注意"裁剪"不只是删消息那么简单,如果上一轮助手引用过早期的内容,粗暴裁掉会导致后续回答失去上下文。经验法则是:单轮控制在窗口的 70% 以内,给输出留足空间。
6. 把调用封装成可复用模块:三个小技巧让代码从能跑到能上线
6.1 用 yield 把流式响应包成生成器
第 4 章的stream_chat函数本身就是生成器,这带来一个重要的灵活性:调用方可以自由控制消费节奏。你可以把它接到命令行工具里逐字打印,也可以接到 Web 框架里把增量转发给前端。封装的要点是保持生成器接口纯净,不要把打印、渲染这类副作用写进生成器内部。一个更顺手的做法是再加一层薄封装,让调用方只需要传入messages。
6.2 重试与退避的幂等设计
把重试逻辑抽成一个装饰器,给所有 API 调用统一加上。重试只对 429 生效,对其他 4xx 错误直接抛出,避免把参数错误也包装成网络问题反复重试。退避时间用2 ** attempt计算,并加上随机抖动,防止并行任务集体重打。
6.3 成本控制:按 usage 做调用日志
非流式响应可以直接从usage字段拿到准确 token 数,流式响应没有这个字段,所以流式调用的成本只能估算:一种方式是累计所有delta.content的字符数,按比例折算;另一种是定期发一个非流式基准请求来校准。日志至少记录每次请求的时间戳、模型、输入 token 估算值和输出 token 估算值,这样月底对账时不用翻聊天记录。
我自己第一次把流式接进线上服务时,就是没给读取超时单独设值,半夜一个长回答任务把整个 worker 拖死,日志里全是 ReadTimeout,重启后又被同样的问题打挂,后来把所有请求统一收拢到带重试、带超时、带日志的模块里才踏实。这套封装方法就是上面三个技巧的组合,当时如果早一步这么做,能省掉一整夜排查的时间。希望帮到你。
本文还有配套的精品资源,点击获取