
简介围绕DeepSeek API调用全流程这一DOCX文档为有一定编程经验的开发者和研究人员提供了一条从注册、获取API Key到实现流式输出的完整路径。文档从DeepSeek官网账号注册讲起涵盖环境准备与pip安装重点演示了使用OpenAI SDK与直接发送HTTP请求两种调用方式并详细解析了JSON响应处理、错误信息识别以及temperature、top_p等核心参数的调节方法。针对实际集成需求文档还专门讲解了流式输出与多轮对话的配置技巧便于支撑聊天机器人、客服系统、智能助理等文本交互项目。整份资源为单个DOCX文档压缩后仅13KB内容紧凑、步骤清晰并附有可直接修改的Python代码片段和官方文档指引。这份文档已有3152人学习/下载对于希望快速上手DeepSeek API并集成到自身产品或服务的开发者是一份低门槛且即学即用的入门手册。1. 为什么你需要这份 DeepSeek API 调用指南从注册到流式输出的完整链路DeepSeek API 实际用起来并不复杂但卡住的人往往集中在同一两个位置注册后找不到正确的请求路径、非流式响应读取了错误的字段、开了 stream 却一直接不到内容。这篇指南从我接入 DeepSeek 的真实顺序出发把注册、拿钥匙、普通请求、流式输出这几个环节按可复现的方式拆开每一步都给出代码和不带滤镜的避坑说明。适合正在把 DeepSeek 接进脚本或小应用的开发者也适合第一次接触流式输出的初学者能帮你用最短路径跑通第一个完整对话。2. 从注册到拿到 API Key凭证管理、额度查看与三种常见失误2.1 注册、身份认证与 API Key 的正确打开方式先去 DeepSeek 官网注册账号用手机号或邮箱都行按控制台的引导把基础信息验证做完这一步没有太多技巧真正容易出问题的是登录后。进到控制台首页左侧导航会看到 API Keys、用量信息、模型列表等入口其中 API Keys 就是你要长期打交道的地方。我第一次用的时候犯过一个低级错误以为 API Key 是登录密码的延伸把账号密码填进了 Authorization 头。实际上 API Key 是一串以 sk- 开头的独立字符串它和你的登录密码互不相干只负责标识哪个应用在调用这个接口。在创建密钥时页面会要求填一个名字例如 prod-automation 或 test-local这只是方便你辨识用途并不影响计费和权限。创建完成后完整密钥正文只会展示一次关掉弹窗或刷新页面之后就再也没法查看明文这一点让不少人措手不及。遇到这种情况处理方式只有一个在控制台吊销旧 Key重新生成一个把新值立即存到安全的地方。另一个常见误区是把 API Key 和模型名混为一谈。API Key 是凭证模型名只是请求体里的一个字符串参数比如 deepseek-chat 或 deepseek-reasoner。两者的关系类似于“门禁卡”和“你要去几楼”——一个管能不能进一个管进哪间房代码里负责这两个信息的变量完全不同。2.2 密钥存储别把 sk- 写进仓库泄漏了怎么办正确姿势是把密钥放到环境变量里。在 Linux 或 macOS 上临时设置环境变量用一行 exportexport DEEPSEEK_API_KEYsk-xxxxxxxxxWindows 的 PowerShell 对应命令是$env:DEEPSEEK_API_KEYsk-xxxxxxxxx但这样设置只在当前终端会话生效新开窗口就没了。更省心的方案是用项目根目录下的 .env 文件管理密钥配合 python-dotenv 这类工具读取# .env DEEPSEEK_API_KEYsk-xxxxxxxxximport os from dotenv import load_dotenv load_dotenv() api_key os.getenv(DEEPSEEK_API_KEY)逻辑说明环境变量把密钥从代码里剥离出来代码入库时不必担心把明文带进仓库。load_dotenv 会读取当前目录下的 .env 文件并写入进程环境变量之后 os.getenv 就能拿到对应值。注意 .env 文件本身也要写进 .gitignore否则等于白藏。如果你已经把密钥提交到 GitHub不要以为删掉文件就完事仓库提交历史里还能翻出来正确做法是立刻去控制台把旧 Key 吊销重建。这里有没有后悔药严格说没有唯一能做的就是快速让旧 Key 作废。平台通常也提供删除单个 Key 的入口我建议平时只维护一把开发 Key 和一把生产 Key分环境使用避免“一把钥匙开所有门”造成的排查困难。提示任何日志、请求体模板、前后端联调的抓包文件里都不应该出现完整 Key监控台上看到 sk- 开头字符串就要警惕。2.3 模型选择与额度消耗先看两个入口再写代码写第一行代码之前建议先打开控制台的模型列表确认当前开放的模型入口和计费方式。DeepSeek 目前常见的是 deepseek-chat 和 deepseek-reasoner 两个入口前者面向通用对话和中短文本生成后者会先输出一段内部推理过程再给出最终回答适合数学、逻辑、代码排查这类复杂任务。用聊天机器人或写总结工具deepseek-chat 就能覆盖硬上 reasoner 反而可能因为多一段思考而拖慢首字返回。计费方面我一般直接以控制台显示的单价为准不做内存中的价格猜测。这里有几个不会过时的原则按 token 计费一个 token 约等于一个汉字或半个多英文单词实际消耗必须用响应里的 usage 字段统计而不是靠估算控制台会提供用量面板建议跑通第一个请求之后点进去看一眼消耗是否正常。很多免费接口用着用着额度没了就是因为没人关心 usage 里 prompt_tokens 和 completion_tokens 的比例。下表是我对两个入口在实测中经常遇到的差异的总结对比项deepseek-chatdeepseek-reasoner典型用途对话、文案、总结推理、复杂解题、代码设计首字返回速度相对更快通常更慢先输出思考内容流式返回内容主要读 content可能先出现 reasoning_content上下文裁剪策略常规滑窗即可保留思考过程会加倍消耗 token拿表格里的流式字段差异来说很多人换到 reasoner 后突然发现前面一大段“没有内容”其实是推理过程走了另一个字段。这个细节到第 4 章再展开先记住一件事选模型入口会影响你能读到的返回字段也会影响你的 token 账单。3. 先跑通非流式请求chat/completions 的最小调用与参数拆解3.1 用 requests 发一个最小请求并确认返回提交把所有参数讲一遍先给一段能直接运行的最小脚本。它做的事情只有一个把用户的提问发给 chat/completions 接口打印状态码和 JSON 响应。import requests api_key sk-xxxxxxxxx url https://api.deepseek.com/chat/completions payload { model: deepseek-chat, messages: [ {role: system, content: 你是一个简洁的助手回答问题不超过三句话。}, {role: user, content: 用一句话说明什么是流式输出。}, ], stream: False, } resp requests.post( url, jsonpayload, headers{Authorization: fBearer {api_key}}, timeout60, ) print(resp.status_code) print(resp.json())逻辑说明这个接口要求的鉴权方式是把 Key 放到请求头的 Authorization 字段前缀必须是 Bearer 加一个空格。有些同学习惯把一个叫 api_key 的字段塞进 body这是不生效的。请求路径可以写 https://api.deepseek.com/chat/completions也可以拆成 base_url 加后缀的方式关键是不要丢掉 /chat/completions 这节。timeout 参数的意义是防止程序无限等待普通非流式请求我一般设 60 秒如果后端没有在期限内返回requests 会抛 ConnectTimeout 或 ReadTimeout 异常而不是让整个进程挂死。参数说明messages 数组里的 role 只允许 system、user、assistant 三种。system 用于设定整体行为user 是用户输入assistant 代表模型历史回答。连续多轮对话时要把历史消息按顺序放进 messages模型本身没有记忆所有上下文都靠这个数组传递。3.2 必调与易错参数model、temperature、max_tokens跑通最小请求后接下来值得深究的参数有三个。第一个是 model它决定你调用哪个模型入口写错名字会直接返回 400 Invalid model。第二个是 temperature它控制生成结果的随机性。官方文档会给出允许范围我自己的经验是写文案调到 0.9 以上更发散代码生成调到 0.3 以下更保守默认值大约 0.7 比较均衡能覆盖大部分场景。注意这个参数不是越高越聪明它影响的是采样的概率分布并不是模型能力本身。第三个是 max_tokens它限制的是本次回答最多生成多少 token而不是输入侧的上下文长度。很多人把这两件事搞混以为设个 4096 就能让模型记住更多历史实则恰恰相反——输入一侧由模型的最大上下文长度决定输出一侧由 max_tokens 决定两边是独立的。如果 max_tokens 设太小回答会被硬截断响应里 choices[0].finish_reason 会变成 length而不是 stop这是区分“说完”和“被截断”的最直接信号。还有一个潜在雷区temperature 填了允许范围之外的值接口不会温和地忽略它会直接返回 400。项目上线前建议对这几个参数做一层校验避免用户传入离谱数值把请求打崩。3.3 读懂响应choices、finish_reason 与 usage一个典型的非流式响应长这样{ id: chatcmpl-123456, choices: [ { index: 0, message: { role: assistant, content: 流式输出就是服务器把回答拆成小段逐字推送给客户端。 }, finish_reason: stop } ], usage: { prompt_tokens: 32, completion_tokens: 36, total_tokens: 68 } }读取最终文本的正确路径是resp.json()[choices][0][message][content]。新手最容易翻车的点是两处一是以为数据在 data 字段里绕半天找不到 content二是忘记 choices 是一个数组上来就直接resp.json()[choices][message]然后拿到一个 TypeError。只有当你在请求里设置了 n 大于 1 时choices 里才会出现多个候选绝大多数场景只需要取第 0 个。finish_reason 这个字段也值得多看一眼。stop 表示模型认为回答已经完整length 表示因为 max_tokens 或上下文限制被截断content_filter 表示内容被后处理过滤这种情况需要检查提示词本身是否触发了平台的内容策略。usage 字段则记录这次请求消耗了多少 token做成本统计或账单核对时离不开它。非流式调用的结构相对简单把这一层理顺了再切到流式就容易理解为什么数据结构会“变样”。4. 流式输出落地SSE 协议解析与逐 token 拼接实现4.1 为什么要切流式首字延迟与长任务体验非流式请求的时序很直接你等多久取决于模型生成整段回答要多久。一个 1000 字的回答在人多的时段可能等上十几秒用户盯着页面空白体验上已经超出“可接受”的范围。流式输出的原理是请求建立后不立刻返回完整响应而是由服务端沿着这条 HTTP 连接按事件流持续推送数据片段每生成一个 token 就推一段。用户体验到的效果是内容“打出来了”而不是“卡住不动”。衡量流式体验有个指标叫首字延迟指从请求发出到收到第一个内容片段的时间。非流式请求必须等完整回答生成完才返回首字延迟等于总延迟流式请求往往几百毫秒就能看到第一句话。所以聊天机器人、工单助手、长文本总结这类任务我一般默认切流式。离线批量处理反而是非流式更省心因为不需要处理断流拼接和重试逻辑。SSE也就是 Server-Sent Events是流式场景最常见的传输格式。服务端按行推送事件每个数据块以data:开头块与块之间用空行分隔最后一个块是data: [DONE]表示生成结束。这里的细节是虽然网络层拿到的是一个连续的字节流但 requests 的 iter_lines 会帮我们按行切好大大降低了手动拼包的难度。4.2 requests 手写流式逐 token 拼接的最小代码先把上一章的最小请求改成流式版本对比着看你就能发现结构差异import requests import json api_key sk-xxxxxxxxx url https://api.deepseek.com/chat/completions payload { model: deepseek-chat, messages: [ {role: user, content: 讲一个 80 字以内的冷知识。} ], stream: True, } resp requests.post( url, jsonpayload, headers{Authorization: fBearer {api_key}}, streamTrue, timeout(10, 120), ) collected [] for line in resp.iter_lines(): if not line: continue line line.decode(utf-8) if not line.startswith(data:): continue data_str line[5:].strip() if data_str [DONE]: break chunk json.loads(data_str) if not chunk.get(choices): continue delta chunk[choices][0].get(delta, {}) content delta.get(content, ) if content: collected.append(content) print(content, end, flushTrue) print(\n拼接结果, .join(collected))逻辑说明请求体里加了stream: True这会让接口切换为 SSE 推送模式响应不再是单一 JSON而是多行流式文本。resp.iter_lines() 返回的是按行切分后的迭代器空行直接跳过非 data 开头的行也跳过。注意这里 decode 用的是 utf-8如果响应里有别的编码会导致乱码不过这类接口一般都用 utf-8。遇到[DONE]就 break循环正常退出收集到的文本片段拼起来就是完整回答。参数说明timeout 我改成了元组(10, 120)第一个值代表连接超时 10 秒第二个值代表单次读取超时 120 秒。为什么不能沿用上一章的非流式 60 秒因为流式响应可能持续几十秒甚至更久如果读超时设得太短模型还在生成本地却先抛出 ReadTimeout好好的流式请求就变成“断流”了。4.3 解析边界event 行、空 delta 与 reasoning_content手写 SSE 解析时会碰到几个边界情况这也是很多流式代码看起来正常、实际却收不到数据的原因。第一类是 event 行。完整的 SSE 事件可以包含 event、data、id 等多个字段格式类似于event: message data: {choices:[{delta:{content:你}}]}iter_lines 会把上面两行都返回如果代码只判断“data: ”开头event 行会被跳过这没问题但如果有人写了严格的 JSON 解析把所有行都丢给 json.loadsevent 行会直接抛异常导致程序在最开始就退出。处理方式是按前缀过滤只对 data: 开头的行做 JSON 解析。第二类是空 delta。流式的第一个 chunk 往往不是正文而是先推送一个包含 role 的 deltacontent 字段此时是空字符串。如果你在循环里只写了“内容为空就 break”程序会在第一个 chunk 就退出打印出空白结果看起来就像“没返回”。正确逻辑是只有收到[DONE]才 break内容为空就跳过本轮。第三类是 reasoning_content。deepseek-reasoner 这类推理模型的流式返回里思考过程会先出现在 reasoning_content 字段最终回答才出现在 content。如果只读 content你会看到先静默几秒然后突然源源不断地输出内容实则前面的推理过程全被丢了。想要完整展示思考链路就需要在循环里单独收集 reasoning_content再决定是渲染还是丢弃。4.4 用 OpenAI SDK 替代手工解析的接入路径手写 requests 的好处是透明坏处是边界情况都得自己处理。如果你的项目已经基于 openai Python 库可以直接把 base_url 指向 DeepSeek用同一套代码跑通流式from openai import OpenAI client OpenAI( api_keysk-xxxxxxxxx, base_urlhttps://api.deepseek.com, ) stream client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 用一句话介绍杭州。}], streamTrue, ) for chunk in stream: if chunk.choices: delta chunk.choices[0].delta if delta and delta.content: print(delta.content, end, flushTrue)逻辑说明SDK 底层走的依然是 SSE只是把按行解析、JSON 反序列化、空行处理都封装了。base_url 只需要写到域名级/chat/completions 后缀由 SDK 自行拼接。如果你用的 openai 库是老版本参数命名和返回类型会有明显差异建议先升级到当前最新版本再跑这段代码。SDK 封装的代价是出错时你不太容易看清原始数据我遇到诡异问题时还是会抓一次原始响应来定位。5. 调用 DeepSeek API 的 5 个高频坑状态码、上下文长度与断流排查5.1 400上下文长度超限报错数字代表什么现象多轮对话进行到某个节点接口突然返回 400错误信息形如this models maximum context length is 1048576 tokens。数字下面的提示会告诉你当前请求折算成多少 token两者一对比问题就很明确。原因输入侧把系统提示、多轮历史、当前问题全部拼进 messages累计长度超过了模型允许的最大上下文。注意这里数的是 token不是字数。一个汉字通常折算成 1 到 2 个 token英文单词按词根和空格切分长对话很容易迅速逼近上限。解决写一个上下文裁剪函数。保留 system 提示和最近 N 轮对话把更早的内容直接丢弃。如果业务需要理解全局信息就把早期对话先交给模型压缩成摘要再作为一条摘要消息放进 messages而不是一股脑全塞。有一个更直观的调试技巧在报错前先打印sum(len(m[content]) for m in messages)粗略换算 token 量能提前预估阈值。5.2 401/403密钥失效与组织禁用怎么定位现象请求返回 401响应体提示 invalid api key偶尔也会碰上 403提示组织被禁用或找不到该组织下的有效路由。搜索里出现过的no api key for provider route、this organization has been disabled都属于这一类。原因401 基本是密钥本身的问题比如复制漏了字符、把旧 Key 当成新的、代码里读到的环境变量是空的。403 则更偏向账户层面组织被禁用或账户状态异常需要回控制台确认。解决不要第一时间改代码先用 curl 复现一次把网络链路和代码隔离curl https://api.deepseek.com/chat/completions \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -H Content-Type: application/json \ -d {model:deepseek-chat,messages:[{role:user,content:hi}],stream:false}如果 curl 返回 200说明密钥和路径都没问题问题出在代码侧如果 curl 也返回 401直接去控制台重新生成一个 Key 再试。403 场景则要检查账户状态和当前组织必要时联系平台侧处理这个过程和代码无关改代码只会浪费时间。5.3 流式断流输出到一半停住怎么办现象流式响应前几十个 token 正常生成到一半突然不再有新数据程序既不退出也没有报错或者表现为等了一阵子才抛超时异常。原因常见的有三类。一是读超时设得太短模型还在生成本地先等不及了二是中间网络设备或网关在同一连接上设置了空闲超时一段时间没数据就掐断连接三是解析代码对异常数据行抛错后被 try/except 吞掉表面看是断流其实是异常处理把真正的问题藏住了。解决读超时提到 120 秒以上给长生成留足时间。循环内部的异常不要直接吞至少打印 chunk 原始信息。如果断流已经发生常见做法是把已收集的文本拼到 messages 里发一个新请求让模型继续写提示里写明“从以下内容继续”。需要说明的是续写不是不走官方断点续传因为多数兼容接口没有独立的续传机制这种做法只是工程上的兜底方案。5.4 429 限流退避间隔不能拍脑袋现象高频请求或并发量上来之后接口开始返回 429响应头里往往带 Retry-After。原因平台对单位时间内的请求数或并发连接数有限制超过阈值就拒绝服务另一种可能是账户余额或免费额度不足也会导致 429 或类似错误。解决优先读响应头里的 Retry-After按照服务端建议的秒数等待。如果没有这个头用指数退避第一次等 1 秒失败后翻倍2 秒、4 秒、8 秒最多 60 秒超过上限就放弃并写入日志。并发场景更合理的做法是加一层简单队列控制同时发出的请求数而不是依赖退避和重试。我曾见过一个任务里用 while True 硬重试服务端没限流反而被自己的请求打挂了这就是没有退避策略的典型翻车现场。5.5 空 choices 与读超时两个孪生现象不要误判现象stream 改成 True 之后程序运行完却不打印任何内容另一种情况是流式输出每隔固定时间就超时断开。原因前者大概率是流式首个 chunk 里的 delta 只有 rolecontent 还是空而解析代码里写了“content 为空就 break”于是第一轮就退出了。后者则是对流式请求使用了非流式的超时设置比如沿用 5 秒连接超时导致模型生成稍慢就断。解决无论是手写 requests 还是 SDK流式循环都只在[DONE]或连接断开时退出不能以空 content 作为结束条件。超时参数设置为连接与读取分离的元组形式给读取阶段留够时间。判断这两个问题有个快速方法把收到的第一个 chunk 原样打印出来看它到底长什么样再决定解析逻辑别猜。6. 用 JSON 模式与工具调用约束输出并验证流式结果正确性6.1 response_format 与 tools 参数把模型输出纳入业务流程如果你的程序需要解析模型返回的文本别让它自由发挥。一个常见做法是在请求体里加response_format: {type: json_object}模型会尽量生成合法 JSON 对象配合系统提示里明确写“输出 JSON 对象”解析时就不用面对各种口语化表达。工具箱里再用 tools 参数声明函数签名模型就能在合适的时候返回 tool_calls由你的代码执行真实函数把结果回传后继续对话tools [{ type: function, function: { name: get_weather, description: 查询指定城市的天气, parameters: { type: object, properties: {city: {type: string}}, required: [city], }, }, }] resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 北京适合跑步吗}], toolstools, tool_choiceauto, ) print(resp.choices[0].message.tool_calls)逻辑说明tool_choice 为 auto 时由模型自己判断是否调用工具适合多数场景如果你只想让模型做某个特定分支也可以显式指定 tool_choice 为对应函数名。拿到 tool_calls 后程序去查询天气再把查询结果作为一条新的 assistant 或 tool 消息追加进 messages模型才能据此生成最终回答。注意调用链别写成死循环给工具调用次数设上限。6.2 流式回放对比上线前必做的完整性检查我验证流式解析是否正确的办法是同一提问分别跑 streamfalse 和 streamtrue把两份结果存下来对比。流式拼接后的字符串和非流式完整回答不一定逐字一致但内容主体应该重合如果流式结果少了后半段那就是循环里提前 break 或断流重试没接上。def test_stream_matches_nonstream(): non_stream call_api(streamFalse) streamed call_api(streamTrue) assert streamed non_stream, 流式拼接内容不完整这条断言放进回归脚本比肉眼检查可靠得多。接入流式之后我习惯每个接口都留一个对比开关开发阶段打印原始 chunk上线阶段关闭出问题再打开。这些细节不会让你更快地写完第一版但能省掉不少上线后半夜查日志的时间。希望帮到你。本文还有配套的精品资源点击获取