ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DeepSeek API + Python 接入避坑:版本、密钥与流式输出实战

DeepSeek API + Python 接入避坑:版本、密钥与流式输出实战

简介:这是一份面向具备一定 Python 基础、希望快速接入 DeepSeek 语言能力的开发者与研究人员的实战型 API 调用指南,聚焦从账号注册、密钥获取到接口集成的完整上手路径。内容以单个 docx 文档承载,压缩包约 13KB,轻量便于查阅,覆盖环境准备、OpenAI SDK 与原生 HTTP 请求两种调用方式,并延展到请求响应解析、temperature、top_p、max_tokens 等参数调节、stream=True 流式输出和多轮对话上下文维护等常见开发需求。已有 3165 人学习,说明其在中文 AI 接口入门场景中具有一定参考价值。读者可从中获得可直接改造的 Python 示例代码、429 或 503 等异常处理与速率限制提醒、密钥安全及使用规范说明,以及官方文档查阅线索。其内容适合聊天机器人、客服系统和智能助理等文本交互项目的初期验证与功能集成,也可作为后续复杂业务封装的代码底稿。

1. 装完 SDK 跑不通,多半是版本没对齐

一位开发者把示例代码复制进编辑器,pip install openai也执行完了,回车之后终端甩出一句AttributeError: module 'openai' has no attribute 'ChatCompletion'。他第一反应是密钥错了,换了三个 Key,又把网络检查了一遍,问题依旧。真正的原因在版本:openai 1.x 之后,旧的ChatCompletion.create入口已经挪走,代码和库对不上,报错信息就会把人往错误方向带。

接 DeepSeek API 这套环境门槛不高,Python 3.8 以上、一个 SDK、一个 API Key 就能跑起来,但版本、base_url、密钥这三处只要错一处,新手很容易卡半天。下面按实际装机顺序走:注册拿 Key、装依赖、两条调用路线、参数与流式输出,最后给验证方法和踩坑清单。

2. 注册与密钥:从创建 API Key 到写进环境变量

2.1 账号注册与 API Key 的创建路径

入口在 DeepSeek 官网,页面上有「API 开放平台」和「开始对话」两个入口,要拿 Key 就走前者。注册登录之后,左侧或账户菜单里能找到「API Keys」,点进去创建。整个流程没有什么技术含量,但有两个细节值得强调:一是 Key 创建后只在页面上完整显示一次,关掉就看不到了,必须当场复制;二是同一个账号不要反复创建 Key,能复用就复用,创建多了后期排查调用来源会变成一团乱麻。

我一般的操作顺序是这样:

  1. 打开官网,进入「API 开放平台」,完成账号注册与登录。
  2. 在「API Keys」页面点创建,给 Key 起一个能看出用途的名字,比如local-dev、project-a-test。
  3. 复制出来的字符串先粘到本地一个临时文本里,别直接贴进代码文件。
  4. 立刻写进环境变量或密钥管理工具,确认可用后再把临时文本删掉。

第 4 步是很多人省掉的,结果就是 Key 散落在各种demo.py、test.ipynb里,某天提交代码时一起推上去了。这类事故在团队里出过一次就够难受的。

2.2 Python 环境与 openai SDK 的安装

原教程给的门槛是 Python 3.8 或更高。实际装的时候我建议直接上 3.10 或 3.11,3.8 已经进入生命周期尾声,很多依赖包的新版本不再为它提供轮子,装 SDK 时容易碰上编译报错。版本确认和虚拟环境创建一起做:

# 确认解释器版本,低于 3.8 直接换 python3 --version # 建独立虚拟环境,避免污染系统 Python python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 升级 pip,老版本 pip 解析依赖时容易失败 python -m pip install --upgrade pip # 装 SDK 和 HTTP 调用要用的 requests pip install "openai>=1.0" requests # 确认装上的实际版本 pip show openai

pip show openai这一句很关键,它输出的Version决定了你该用哪套写法。DeepSeek API 与 OpenAI 的接口形态兼容,所以官方示例里既给了 SDK 写法,也给了裸 HTTP 写法。SDK 的好处是异常类型、重试、超时都有现成封装;坏处是版本迭代快,网上搜到的代码经常是上一代的。

版本线对照放在这里,装之前先对号入座:

版本线安装方式调用入口base_url 参数
0.28 系列pip install "openai==0.28.*"openai.ChatCompletion.createopenai.api_base
1.x 系列pip install "openai>=1.0"client.chat.completions.createOpenAI(base_url=...)

两条线不能混用。项目里如果是老代码,要么把依赖锁在 0.28 系列保持不动,要么一次性改到 1.x 写法,最怕的是锁了 0.28 却复制了 1.x 的示例,报错信息看着像密钥问题,其实是模块里根本没有那个属性。

2.3 密钥不落代码:环境变量的三种写法

API Key 的正确位置是环境变量,不是源码。三种写法按场景选。

第一种,当前 shell 临时生效,适合一次性调试:

# 注意等号两边不能有空格,值不要加引号 export DEEPSEEK_API_KEY="sk-你的密钥" echo $DEEPSEEK_API_KEY | head -c 8 # 只打印前 8 位确认非空

第二种,项目级.env文件,适合本地长期开发。装一个python-dotenv,把密钥写进.env,代码里读环境变量:

import os from dotenv import load_dotenv # 读取项目根目录的 .env,不覆盖已存在的系统环境变量 load_dotenv(override=False) api_key = os.environ.get("DEEPSEEK_API_KEY") if not api_key: raise RuntimeError("DEEPSEEK_API_KEY 未设置,检查 .env 或 shell 环境")

override=False的意思是系统里已经有的变量优先,这样 CI 环境注入的密钥不会被本地文件盖掉。.env必须写进.gitignore,这一条没有商量余地:

echo ".env" >> .gitignore echo ".venv/" >> .gitignore

第三种,CI 或容器环境用平台提供的密钥注入功能,代码侧完全不用改。三者的共同点是代码里只出现os.environ[...],任何把sk-开头的字符串直接写进源码的做法,都是在给自己埋后悔药。

3. 两条调用路线:OpenAI SDK 与原生 HTTP 请求

3.1 用 OpenAI SDK 发第一个请求

1.x 系列的写法是先构造 client,再调client.chat.completions.create。base_url 指向 DeepSeek 的接口地址,SDK 会自己在后面拼路径:

import os from openai import OpenAI # base_url 写域名即可,SDK 内部会补 /chat/completions client = OpenAI( api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com", timeout=60.0, # 单次请求超时,长文本生成别设太小 max_retries=2, # SDK 自带重试,处理偶发连接失败 ) resp = client.chat.completions.create( model="deepseek-chat", # 模型名以控制台文档为准 messages=[ {"role": "user", "content": "你好,请介绍 DeepSeek 的功能。"}, ], ) # 取值路径比 0.28 深一层:choices -> message -> content print(resp.choices[0].message.content)

几个参数值得单独说。api_key从环境变量取,不走字面量;base_url是这套调用能不能通的关键,写错就是 404;timeout在生成长文本时一定要放大,默认值下超时断连很常见;max_retries让 SDK 自己处理连接抖动,比自己写循环省事。

如果手上是老项目,沿用原文给的 0.28 写法也能跑,只是要额外指定接口地址:

import openai openai.api_key = "从环境变量读取,别硬编码" openai.api_base = "https://api.deepseek.com/v1" # 老版本里这个参数叫 api_base response = openai.ChatCompletion.create( model="deepseek-chat", messages=[{"role": "user", "content": "你好,请介绍 DeepSeek 的功能。"}], ) print(response.choices[0].message.content)

同一段逻辑,1.x 里api_base变成构造参数base_url,ChatCompletion.create变成client.chat.completions.create,返回对象的取值路径多了一层message。升级 SDK 时按这三处改,基本不会漏。

3.2 requests 直连 HTTP:不装 SDK 也能调

有些环境不方便装 SDK,比如嵌入式设备、受限的容器镜像,或者你只想验证网络和密钥是否通。这时候直接打 HTTP 接口更直接:

import os import requests url = "https://api.deepseek.com/v1/chat/completions" headers = { "Authorization": f"Bearer {os.environ['DEEPSEEK_API_KEY']}", "Content-Type": "application/json", } payload = { "model": "deepseek-chat", "messages": [ {"role": "user", "content": "你好,请介绍 DeepSeek 的功能。"}, ], "temperature": 0.7, } # timeout 必须显式给,否则网络卡住时进程会一直挂着 resp = requests.post(url, headers=headers, json=payload, timeout=60) # 非 2xx 直接抛异常,避免把错误响应当正常内容解析 resp.raise_for_status() data = resp.json() print(data["choices"][0]["message"]["content"])

这里有三处容易写错。Authorization头是Bearer加一个空格再加密钥,少了空格就是 401;URL 要写完整的/v1/chat/completions,和 SDK 里只写域名不一样;timeout不给,进程在弱网下会一直等,排查时表现为「程序没报错但也不出结果」,很迷惑。

3.3 base_url 与请求地址的对照关系

SDK 和 HTTP 两种写法最容易被混淆的就是接口地址,整理成一张表:

调用方式地址写法说明
OpenAI SDK 1.xbase_url="https://api.deepseek.com"SDK 自动拼/chat/completions
OpenAI SDK 0.28openai.api_base="https://api.deepseek.com/v1"老版本习惯带/v1
requests 直连https://api.deepseek.com/v1/chat/completions路径写全,不拼

我的习惯是:SDK 统一写域名,HTTP 统一写全路径,两套代码各自成文件,不互相抄。混着抄的结果就是 SDK 里写了全路径变成/chat/completions/chat/completions,或者 HTTP 里只写域名少了/v1,两种都是 404,错误信息还长得差不多。

4. 参数调节、响应处理与流式输出

4.1 六个生成参数的实际取舍

接口开放的可调参数不多,但每个都影响输出形态。原教程提到的有temperature、top_p、frequency_penalty、presence_penalty、max_tokens、stop,把它们放进一次完整调用:

resp = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": "写一段产品介绍,控制在 100 字内"}], temperature=0.7, # 0 更确定,1 附近更发散 top_p=0.9, # 与 temperature 二选一调,不要同时大改 max_tokens=512, # 输出上限,设小了会被截断 frequency_penalty=0.0, # 正数抑制重复用词 presence_penalty=0.0, # 正数鼓励引入新话题 stop=["\n\n"], # 命中即停,适合固定格式输出 )

实际调的时候我遵循两条:做结构化抽取、分类、代码生成,temperature压到 0 到 0.3;做文案、对话、头脑风暴,放到 0.7 到 0.9。temperature和top_p不要一起大改,否则输出风格的变化没法归因,调参就变成了玄学。max_tokens按业务给,给对话留 512 到 1024 够用,长文生成再往上加,但要注意它统计的是输出长度,不含输入。

4.2 响应结构与 finish_reason 的读法

一次成功调用的返回体里,最常取的是三样东西:正文、结束原因、token 用量。

choice = resp.choices[0] print(choice.message.content) # 正文 print(choice.finish_reason) # stop / length 等 # 用量统计,做成本核算时按这个累计 print(resp.usage.prompt_tokens) # 输入消耗 print(resp.usage.completion_tokens) # 输出消耗 print(resp.usage.total_tokens)

finish_reason是stop说明模型正常收尾;是length说明撞上了max_tokens上限,正文被硬截断,这时要么调大上限,要么在提示词里明确要求压缩长度。线上出现过「回答只输出一半」的反馈,八成是这个值没看。usage字段建议在日志里落一份,按天聚合就能看出哪个功能在烧额度。

4.3 流式输出:stream=True 的正确消费姿势

对话类产品不开流式,用户要盯着空屏幕等好几秒。SDK 里加一个stream=True就切成逐块返回:

stream = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": "用三句话介绍自然语言处理"}], stream=True, ) for chunk in stream: # 末尾可能出现 choices 为空的块,先判空再取 if not chunk.choices: continue delta = chunk.choices[0].delta if delta.content: # end="" 不换行,flush=True 立刻刷到终端 print(delta.content, end="", flush=True)

delta是增量内容,不是全量,所以只能追加不能覆盖。flush=True少了会出现「内容一次性全出来」,看着像没生效,其实是被标准输出缓冲了。chunk.choices判空也不能省,某些收尾块里没有choices,直接下标会抛IndexError。

4.4 多轮对话:上下文自己维护

接口本身不记录会话,多轮靠每次把历史消息一起发过去。用一个列表累积:

messages = [{"role": "system", "content": "你是一个回答简洁的助手。"}] def ask(text: str) -> str: messages.append({"role": "user", "content": text}) resp = client.chat.completions.create( model="deepseek-chat", messages=messages, temperature=0.5, ) answer = resp.choices[0].message.content # 关键一步:把回答也塞回历史,否则下一轮模型不知道上文 messages.append({"role": "assistant", "content": answer}) return answer print(ask("我叫什么?")) # 第一轮 print(ask("我刚才问了你什么?")) # 能答上来,说明上下文生效

这个列表会越滚越长,token 消耗线性上涨。常见做法是只保留最近 N 轮,或者超出阈值时把早期对话压成一段摘要再塞回 system 消息。流式模式下也一样,只是要在拼接完所有delta.content之后再把完整回答追加进历史,别把半截内容存进去。

5. 踩坑排查:五个高频翻车点

5.1 密钥与环境变量相关的三个坑

坑一:AttributeError: module 'openai' has no attribute 'ChatCompletion'

现象:代码原样复制,导入 SDK 后就报这个错,看着像安装不完整。

原因:装的是 1.x 系列,代码用的是 0.28 写法,入口函数已经不存在了。

解决:pip show openai看版本。1.x 就改成client = OpenAI(...)加client.chat.completions.create;必须留在 0.28 的项目就把依赖写成openai==0.28.*并锁进requirements.txt,别让 CI 每次装最新版。

坑二:401,提示认证失败

现象:Key 明明是对的,换一个也不行。

原因:三种情况最常见——复制时把首尾空格或换行带进去了;export是在 A 终端执行的,运行脚本的是 B 终端;.env文件里写了DEEPSEEK_API_KEY = "sk-xxx",等号两边有空格,或者值被引号包住,读出来带引号。

解决:先echo $DEEPSEEK_API_KEY | wc -c看长度对不对,再在同一个 shell 里跑脚本。.env里等号两边不留空格,值不加引号。HTTP 调用还要检查Bearer后面那个空格。

坑三:404,接口找不到

现象:返回Not Found,或者 JSON 里提示路径不存在。

原因:base_url 写重了或写漏了。SDK 里写了https://api.deepseek.com/v1/chat/completions会变成重复路径;HTTP 里只写https://api.deepseek.com少了/v1/chat/completions。

解决:按第 3 章的对照表统一。SDK 只写到域名,HTTP 写全路径。

5.2 请求与流式输出相关的两个坑

坑四:429 和 503,调用量一上来就断

现象:单条测试正常,压测或批量跑的时候开始报 429,偶尔 503。

原因:429 是触发了速率限制,503 是服务端临时不可用。两者都属于「等一下再试就可能好」的类型,但很多人直接except后pass,请求就静默丢了。

解决:按指数退避重试,给上限,别无限重试。SDK 的异常类型可以直接捕:

import time from openai import RateLimitError, APIConnectionError def call_with_retry(messages, retries=4): for i in range(retries): try: return client.chat.completions.create( model="deepseek-chat", messages=messages ) except (RateLimitError, APIConnectionError): if i == retries - 1: raise time.sleep(2 ** i) # 1s、2s、4s 递增

批量任务还要在客户端做并发控制,并发数压到自己配额的一半以下,比出了 429 再补救省心得多。

坑五:流式输出要么一次性全出,要么中途断掉

现象:stream=True加了,但终端还是等全部生成完才显示;或者长回答流到一半停住不报错。

原因:前者是没加flush=True,标准输出缓冲导致看起来没流式;后者多半是没设timeout或设得太小,长回答超过阈值被连接层掐断。

解决:print(..., end="", flush=True);client 构造时把timeout调到 60 秒以上,长文场景调到 120 秒;流式循环里加try/except,捕获到中断后把已收到的内容先落库,再决定是否重发。

排查顺序我一般是固定的:先curl确认网络和密钥,再看 SDK 版本,再看 base_url,最后看参数。前两步能解决八成问题,直接跳去调 temperature 通常是在浪费时间。

6. 装完怎么验证:三步自检清单

环境装完不等于跑通。我习惯用三步验证,从底层往上层走,每一步失败都能定位到具体一层。

第一步,裸 HTTP 探活。这条命令不依赖任何 SDK,能排除密钥和网络问题:

curl -s -X POST https://api.deepseek.com/v1/chat/completions \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"ping"}]}' \ | head -c 300

返回 JSON 里出现choices就说明密钥和地址都对了。返回 401 回去查密钥,返回 404 回去查路径。

第二步,SDK 最小脚本。把第 3 章的示例存成check_sdk.py,非流式跑一次,确认能打印出正文,再打印resp.usage.total_tokens,确认用量字段存在。

第三步,流式与多轮各跑一次。流式看内容是不是逐字出现,多轮看第二个问题能不能引用第一个问题的内容。

验证项对照如下:

验证项操作通过标准失败先查
密钥有效curl 探活返回含choices的 JSONKey 空格、Bearer 后空格
地址正确SDK 最小脚本正常打印正文base_url 是否重复或缺失/v1
版本匹配pip show openai版本与写法一致0.28 与 1.x 写法混用
流式生效stream=True脚本内容逐字出现flush=True、timeout
上下文生效连续问两个相关问题第二轮能引用第一轮回答是否追加进 messages

我还会额外做一件事:把check_sdk.py留在仓库根目录,改完配置就跑一遍。有次换机器,环境变量没同步,脚本一跑就报错,比等到业务代码报错再回头查省了半小时。从那以后,我每次换环境、升级 SDK、改 base_url,都强制先跑这三步再动业务代码。希望这套流程能帮你少走点弯路。

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

返回列表