
简介针对DeepSeek API调用的入门示例代码包该zip压缩包共4个文件包含两个Python演示脚本、LICENSE与.gitignore整体仅14KB体量轻巧适合刚接触大模型接口的开发者快速阅读与修改。脚本demo.py与demo_loop.py分别演示单次请求与循环调用的基本写法结合通用API调用流程查阅官方文档、获取密钥、构造HTTP请求、解析响应、异常处理、速率限制等可帮助学习者建立清晰的调用框架并将思路迁移到实际业务场景中。示例代码结构简洁注释直接便于在此基础上扩展为批量调用或异步任务无论是个人试验还是项目预研都有直接参考价值。同时包内附带许可证文件便于确认使用范围.gitignore则提示提交代码时需规避密钥等敏感信息。目前已有283人学习下载对于想低成本上手DeepSeek开放接口的读者而言这是一份简洁实用的起步参考。1. DeepSeek API 调用从 demo 压缩包到第一行可用代码如果你下载过“deepseek-demo-master.zip”这种名字的压缩包大概率是想快速验证 DeepSeek API 怎么调用。这类包通常在代码托管平台能搜到作者把最小可用示例打包好但你解压后照着 README 跑第一个坑就来了不是 401 鉴权失败就是缺依赖甚至卡在“模型名写错了”这种最没技术含量的报错上。DeepSeek API 调用本身不复杂它兼容 OpenAI 的报文协议核心就三件事鉴权头、消息体结构、模型名。这篇笔记直接用这个 demo 包的常见形态展开讲清楚解压之后怎么跑通、代码每一行在干什么、参数怎么调以及我踩过的几个翻车点。适合刚接触大模型 API 的开发者也适合想把 demo 改成生产代码的人。2. 跑通 demo 最小环境解压、安装依赖与第一次请求2.1 解压后先看骨架哪些文件决定你能不能跑deepseek-demo-master.zip 这种包解压出来通常不会只有一两个文件。常见做法是包含 README、requirements.txt、一个 .env.example以及 src 或 demo 目录下的 Python 脚本。很多刚上手的人一上来就找 .py 文件直接python xxx.py结果要么报ModuleNotFoundError要么报 API Key 没设置。我一般会先按顺序看三样东西README 里标注的运行步骤、requirements.txt 里的依赖清单、代码里读取 API Key 的方式。读取方式决定了你会不会踩“鉴权失败”的坑。如果 demo 用的是os.getenv(DEEPSEEK_API_KEY)那你就得先设置环境变量或者在调用脚本前用 export 注入如果它支持从 .env 文件读取那你要先把 .env.example 复制成 .env 再填 Key。这两种方式混着用是 demo 跑不通的头号原因。拿到压缩包第一件事不是改代码是把 Key 的读取链路捋清楚。2.2 Python 环境准备与依赖安装demo 基本都基于 Python 3 写的实测 3.9 到 3.12 都能跑问题大多出在依赖安装不完整。先把虚拟环境建起来避免把本机 Python 环境搞乱这一步对要同时跑多个 demo 的人尤其重要。以下是我本地跑这种 demo 包的固定步骤。python3 -m venv venv source venv/bin/activate pip install -r requirements.txt依赖装完先别急着跑打开 requirements.txt 看一眼有没有openai这个库。DeepSeek API 调用最常见的封装方式就是直接用 openai 的 Python SDK然后把 base_url 指向 DeepSeek 的接口地址这个库没装上后面所有代码都会报No module named openai。另一个容易漏的是python-dotenv因为 demo 里如果写了load_dotenv()少了它 .env 文件不会生效API Key 读出来永远是 None。参数说明venv是虚拟环境目录名你可以改成项目名requirements.txt必须在解压后的根目录执行否则路径不对装不到当前环境。装完之后用pip list核对 openai 和 python-dotenv 是否在列表里这一步能省掉后面一半的排错时间。2.3 用手工 Key 发第一个请求不依赖 demo 的验证方法我习惯先把 demo 放一边自己写一个最小脚本验证 Key 和网络通不通。这样做的好处是把问题边界划清楚如果这个脚本通了说明 Key 没问题、网络没问题剩下就是 demo 代码的问题如果这个脚本都报错那就别去改 demo 了先解决 Key 或网络。这个思维方式在处理任何开源 demo 时都能用少做无用功。from openai import OpenAI client OpenAI( api_keysk-你实际的key, # 临时测试可硬编码生产环境必须走环境变量 base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: user, content: 你好用一句话介绍你自己} ], streamFalse, max_tokens100 ) print(resp.choices[0].message.content)这段代码逻辑很直白先创建客户端对象传 base_url 让 SDK 知道请求发到哪里然后调用chat.completions.create发聊天补全请求model 指定用 deepseek-chatmessages 传用户输入stream 关掉表示等完整结果返回。返回的响应对象里choices[0].message.content就是模型生成的文本。参数说明base_url一定要和官方文档保持一致有的老 demo 写的是https://api.deepseek.com/v1实际上不带 v1 也能通但建议以每个请求里实际打印出来的 URL 为准max_tokens100是控制生成长度的不传的话模型按默认值走可能一次性输出很长streamFalse是阻塞式等待拿到完整结果才会往下走。Key 硬编码只适合这种一次性验证脚本跑通后立刻改成读环境变量。3. 读懂 demo 里的调用链路SDK 封装背后的报文结构3.1 纯 HTTP 调用Authorization 与请求体逐个拆开openai SDK 只是把 HTTP 请求包了一层真正发给 DeepSeek API 的报文结构你必须看得懂否则出问题你都不知道往哪个字段查。用最原始的 requests 库写一遍效果一样还能让你看清鉴权和消息体的全部细节。调试阶段我经常用这个方式把 response 原样打印出来。import requests url https://api.deepseek.com/chat/completions headers { Authorization: Bearer sk-你实际的key, # Bearer 后必须有空格 Content-Type: application/json } payload { model: deepseek-chat, messages: [ {role: system, content: 你是一个Python开发助手}, {role: user, content: 帮我看一下这段代码为什么会内存暴涨} ], stream: False, max_tokens: 500 } resp requests.post(url, headersheaders, jsonpayload, timeout30) data resp.json() print(data[choices][0][message][content])这个代码每次调用都把 payload 完整拼一遍适合理解接口但生产环境不建议这么写因为缺了重试和异常兜底。headers 里最关键的是 Authorization前面固定是Bearer注意 Bearer 后面有个空格少了空格服务端解析不出 token直接返回 401Content-Type 告诉服务端你发的是 JSON。payload 里的 model、messages、max_tokens 和 SDK 版一一对应没有任何隐藏字段。参数说明timeout30是 requests 的请求超时时间单位秒不设的话可能一直挂着等响应这在高并发或服务端繁忙时会拖死你的线程resp.json()解析服务端返回的 JSON但如果返回的是错误信息而不是补全结果字段结构会不一样所以生产代码拿到响应后要先判断status_code再解析这个在后面的避坑章里细说。3.2 openai 兼容 SDKdemo 为什么敢只写几行demo 里大多数代码直接用 openai SDK是因为 DeepSeek API 和 OpenAI API 的报文协议完全兼容你只需要替换 base_url 和 api_key剩下的 SDK 全帮你处理。SDK 封装了请求序列化、响应解析、错误类型转换还带了超时控制和流式迭代器。对业务开发来说这是效率最高的方式也是我推荐的方式。from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) def chat(prompt: str) - str: resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], streamFalse ) return resp.choices[0].message.content这里os.getenv(DEEPSEEK_API_KEY)从环境变量读 Key比硬编码安全得多。SDK 在底层帮你做了几件事把 messages 列表序列化成 JSON、在请求头里自动拼上 Authorization、把 HTTP 错误映射成 openai 库的异常类型。所以你只需要关注 model 和 messages 两个字段。注意 base_url 和 api_key 这两个参数名是 SDK 约定的拼错了它不会报错但请求会打到错误地址或带不上鉴权信息表现就是连接超时或 401。参数说明messages 是角色消息列表一般只有三类角色——system 用来设定行为user 是用户输入assistant 是模型历史回复。多轮对话就是把这几类消息按顺序往列表里追加。这个结构是所有兼容 OpenAI 协议的 API 通用的你在 DeepSeek demo 里看到的结构换到别的服务商也能直接用只是 base_url 和 model 名不同。3.3 把 stream 打开demo 没细讲但聊天机器人必用的模式demo 里常把 stream 设为 False因为这样代码最简单拿到完整文本一次性返回。但要做聊天机器人、流式输出效果必须开 stream。服务端会像打字机一样一段一段往外推 token用户体验好很多而且首字延迟低长回答不用干等十几秒。真实项目的聊天功能几乎都用流式我这里演示 demo 里很少写全的部分。from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 给我讲一个技术人相亲的笑话}], streamTrue, max_tokens300 ) for chunk in resp: delta chunk.choices[0].delta if delta and delta.content: print(delta.content, end, flushTrue)流式模式下resp不再是完整的响应对象而是一个可迭代的生成器每个 chunk 包含一小段增量内容。增量内容在chunk.choices[0].delta.content里可能是空字符串所以要做if delta and delta.content的判空。flushTrue让内容不缓冲立刻打印到终端模拟打字机效果。参数说明streamTrue一旦打开返回结构完全变了不能再用resp.choices[0].message.content取文本每个 chunk 里还可能出现delta.role或finish_reason字段finish_reason 在最后一个 chunk 里会出现stop用来判断生成是否完整。如果要做 UI 流式展示需要在前端把这段文本追加到缓冲区而不是每次替换否则会看到内容跳变。4. 按场景调 DeepSeek API 参数temperature、max_tokens 与 stop 的取舍4.1 参数速查表先抄这张表再微调DeepSeek API 调用过程中模型名只决定能力边界真正决定输出风格的是一组采样参数。demo 里通常只写了 temperature 和 max_tokens但实际项目中还需要 top_p、stop、presence_penalty 和 frequency_penalty。这些参数不是随便调的每个都有明确的行为含义而且和业务场景强相关。参数取值范围默认值作用适用场景temperature0~21采样随机性值越低越确定代码生成、数据提取用 0~0.3top_p0~11核采样与 temperature 配合不建议同时改需要可控创造力时配合调max_tokens1~81924096单次生成的最大 token 数按输出长度需求设置stop字符串数组null遇到指定词立即停止生成防输出越界如 \n\npresence_penalty-2~20对已出现过的词做惩罚值越高越鼓励探讨新话题头脑风暴frequency_penalty-2~20对高频词做惩罚值越高越避免重复措辞长文生成table 里面有几组参数要特别注意。temperature 和 top_p 官方建议是改一个就行两个同时调容易互相打架输出变得不可控max_tokens 不是越大越好它直接影响成本和响应时间demo 里给 4096 是为了展示能力上限生产环境按业务给 200~800 就够stop 参数对控制输出格式极其有用比如让模型只返回 JSON可以在 stop 里放一个结束标志。参数说明temperature0 不代表每次输出完全一样在 GPU 上采样仍有一定随机性但语义层面基本稳定适合做抽取、分类这种不能瞎发挥的任务做创意文案、营销标题可以调 0.8~1.2超过 1.5 之后输出容易崩坏出现句子断裂、逻辑混乱。这里有一个很实用的调参顺序先固定 temperature再调 presence_penalty 控制话题发散度最后用 max_tokens 掐长度不要上来就动所有参数。4.2 对话任务里的上下文管理messages 是怎么累积的demo 的多轮对话示例往往只写了两三条消息但真实聊天机器人跑几轮之后messages 数组会越来越大最终触发上下文长度限制。这里的关键认知是API 调用是无状态的每一次请求都要把全部历史消息再发一遍服务端不会帮你存任何会话记忆。所以每轮请求都要重新拼 messages这也是为什么上下文管理直接决定了成本和使用体验。常见做法是维护一个滑动窗口只保留最近 N 条消息。代码上就是给 messages 数组做截断但要小心不能把 system 消息截掉否则角色设定就丢了。我之前踩过这个坑截断函数每次从第 0 条开始砍结果 system 消息被砍了模型立刻从一个客服变成另一个没性格的角色问答质量明显下降。MAX_TOKENS 4096 def trim_messages(messages, max_history20): system_msgs [m for m in messages if m[role] system] history_msgs [m for m in messages if m[role] ! system] if len(history_msgs) max_history: history_msgs history_msgs[-max_history:] return system_msgs history_msgs这段代码的做法是先把 system 消息分离出来避免被误删再对非 system 的历史消息做长度截断只保留最后 20 条。这里截断逻辑是按条数算的不是按 token 数严格一点应该统计每条消息的 token 数量再截。但大多数文本场景下按条数截断加一个 max_tokens 兜底就够用。参数说明max_history是保留消息条数按你的业务量调整如果每轮回答都很长20 条可能已经超了上下文限制那就要缩到 10 条如果想更精细可以用 tiktoken 之类的分词库把每条消息 token 数累加超过阈值就从前往后删。別忘了截断只是应用层策略API 层的上下窗口是模型决定的超了会直接报错后面避坑章里有具体现象。4.3 内容安全与输出约束stop、max_tokens 与惩罚项的正确姿势业务接入 DeepSeek API 调用后不能把模型输出当黑匣子必须有约束手段。最常见的是用 stop 参数掐断生成比如你想让模型只输出 JSON不去解释、不去客气就可以在 stop 里加 \n\n 或者 。模型生成到 stop 标记时会立即停下省 token 也省时间。另一个被忽略的参数组合是 presence_penalty 和 frequency_penalty。这两个值越高模型越不想重复已经出现过的内容但也越容易让回答显得跳脱值设为负数模型会倾向用重复的表达反而更有固定风格。实测做小红书文案这种需要风格统一的场景frequency_penalty 设 0.2~0.5 效果不错做代码注释生成直接设 0 就好不需要发散去写新话术。resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 把这一段会议纪要整理成三个要点}], temperature0.1, max_tokens300, stop[\n\n], presence_penalty0.2, frequency_penalty0.1 )这里 temperature 给到 0.1 是为了让要点整理这种抽取式任务尽量稳定出结果avoid 模型自由发挥stop 里放了空行标记模型写第一个要点和第二个要点之间有空行时会停下来你拿到的就是干净的三点列表。实际上 stop 的触发是匹配到字符串即终止这不代表它会删除已经生成的部分所以拿到的文本里可能还带一个空行解析时要做 strip。参数说明stop 数组最多可以传 4 个字符串每个都要精确匹配常见用法是传 \n、\n\n、. 这种标点符号但要注意别传太短的字符比如单传一个空格模型几乎每一两秒就触发一次生成内容直接被截断到没有意义。调这个参数时先打印一两次不带 stop 的完整输出看它自然终止在什么位置再去设置对应的 stop 值这才是靠谱的操作顺序。5. DeepSeek API 调用常见问题与避坑记录5.1 401 鉴权失败Key 复制少了字符或带进了换行现象是请求发出后立刻返回 401响应体里写着 Invalid Authentication但代码检查了 Key 看着没问题。这个问题的隐藏原因基本在两个地方复制 Key 时少了最后几个字符或者终端粘贴时把换行符带了进去。尤其从网页控制台复制 Key复制完末尾会有一些不可见字符打印出来也不容易发现。解决方法是不要在代码里直接比对 Key 的字符串而是打印它的长度和最后一个字符。正确 Key 的长度是固定的少了两位以上基本就是复制不完整如果长度对但还报错用repr(key)看末尾是否多了\\n。我在本地调试时习惯把 Key 先写进一个临时文件再用cat读取确保不经过终端剪贴板这种方式能排除绝大部分人为复制问题。5.2 429 限流与并发配额demo 压测翻车现场现象是脚本单次调用正常一旦用并发循环连续调用前面几次成功后面突然报 429 Too Many Requests。原因是对 DeepSeek API 调用频率和并发有配额限制demo 不会把配额写进注释里很多人把它当成无限制接口去跑循环压测很快就打到上限。尤其for循环里不加 sleep几秒钟发几十个请求必被限流。解决方式是先查询你当前账号的速率限制然后按限制调整请求间隔。最简单是代码里加time.sleep(0.5)或用线程池限制最大并发数更稳的是对 429 做指数退避重试。重试前先读响应头里的Retry-After字段如果服务端告诉你要等多久就按这个时间等否则自己按 1、2、4 秒递增重试。压测之前把配额搞清楚是每个开发者的基本素养。5.3 输出被截断max_tokens 没给够或 stop 设错位置现象是长文本生成到一半就停了内容最后一句明显没写完检查返回数据里finish_reason为length而不是stop。finish_reason是判断截断类型的官方指标length表示 max_tokens 耗尽或触顶stop表示正常结束或命中 stop 标记。demo 里如果没打印这个字段很多人会误以为模型生成完了。原因是 max_tokens 设置值小于实际需要的输出长度。解决方式先按输出字符数估算 token中文字符大概 0.6~1 token 一个英文约 1 token 一个单词再加 20% 余量。如果业务上无法预估长度就把 max_tokens 调到模型最大值同时在前端做“生成中”状态提示而不是依赖它一定能一次输出完。反过来如果 finish_reason 是 stop 但内容还是断了那就是 stop 参数里的字符串过早匹配把 stop 数组里太短的条目删掉再试。5.4 上下文长度越界多轮对话历史堆太多现象是多轮对话进行到十几轮后突然报错提示 context length exceeded 或类似的超限错误。原因是 messages 数组里累积的历史太多token 总长度超过了模型单次请求的上限。demo 的循环对话示例几乎没有做历史清理跑几轮没问题跑久了必爆。这个问题在长文档问答场景里尤其明显因为单条 user 消息就可能塞进几千 token两三轮就超限了。解决方式是在应用层做两层保险第一层按条数截断历史保 system 消息第二层按 token 数估算超过阈值就从最旧消息开始删。更优解是做上下文摘要把早期对话用模型概括成一段摘要塞到 system 消息里替代原始历史。这个方案工程量大但效果好能支持真正长时间运行的会话场景。别指望 API 侧会帮你自动精简历史它只按你给的消息列表执行。5.5 JSON 解析报错模型输出不是合法 JSON现象是你在 prompt 里要求“只输出 JSON”但返回内容里夹了 markdown 代码块、开头有废话、结尾多了逗号json.loads直接抛异常。原因是模型输出遵循的是概率分布prompt 指令不是硬约束它可能会带上 json 标记或说明性文字。demo 里如果直接把响应交给 json.loads必然不定期翻车。解决方式不是改 prompt 去祈祷而是写一个健壮的解析函数。先把响应里两个 之间的内容提取出来再去掉首尾空白最后用json.loads解析失败时用正则抠出最外层大括号再试一次。我在生产代码里把这套逻辑封装成了一个函数后续不管换什么模型都不怕输出格式漂移。import json, re def parse_json(text: str) - dict: text text.strip() code_block re.search(r(?:json)?\s*(.*?), text, re.DOTALL) if code_block: text code_block.group(1).strip() try: return json.loads(text) except json.JSONDecodeError: start, end text.find({), text.rfind(}) if start ! -1 and end start: return json.loads(text[start:end1]) raise ValueError(无法从模型输出中解析JSON)这个函数先把常见的 json 代码块包裹去掉然后尝试直接解析失败后用首尾大括号截取子串再试。re.DOTALL让正则里的.能匹配换行不然多行 JSON 就匹配不到。参数上没太多可调的核心思路是多级降级而不是一次解析定生死。实测这个函数能消化九成以上的格式漂移输出剩下的直接抛错并让上层走重试流程。6. 把 demo 改造成可上线的调用骨架三个进阶技巧6.1 统一请求封装超时、重试与日志一次解决demo 里的调用函数是裸的没有超时控制没有重试异常只打印不处理。上线前必须包一层统一入口把超时、重试、日志都收拢。我一般会封装一个ask_deepseek函数所有模块调用它不在业务代码里直接 new OpenAI client。这样以后改模型名、换接口地址只动一处。import logging, time from openai import OpenAI client OpenAI(api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com) def ask_deepseek(messages, retries3, **kwargs): for attempt in range(retries): try: resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, timeout30, **kwargs ) return resp.choices[0].message.content except Exception as e: logging.warning(f第{attempt1}次调用失败: {e}) if attempt retries - 1: time.sleep(2 ** attempt) raise RuntimeError(DeepSeek API 调用失败)思路是失败时按 1、2 秒退避重试最多三次。timeout30在 SDK 里可以直接透传底层对应 HTTP 超时。日志统一记到 warning方便后续排查。这个封装牺牲了一点灵活性但换来的是全项目调用行为的统一线上排查翻日志时非常舒服。6.2 流式响应接入 UI事件回调与消息解析流式接口返回的 chunk 不是整段文本UI 需要逐段更新。如果直接把 demo 的流式代码搬到 Flask 里返回给前端前端要自己处理 SSE 协议比较麻烦。常见做法是后端把流式结果逐段 push 到消息队列前端通过 WebSocket 或 SSE 接收。这里给出一个最简单的生成器版本适合 FastAPI 的 StreamingResponse 或 Flask 的 Response。def stream_chat(messages): resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, streamTrue, temperature0.7 ) for chunk in resp: delta chunk.choices[0].delta if delta and delta.content: yield delta.content.encode(utf-8)这个生成器每次产出一段二进制文本上层可以直接喂给响应流。关键点是控制编码因为网络传输要字节流。如果要做更细粒度的事件类型区分可以检查chunk.choices[0].finish_reason等于stop时推送一个结束事件前端借此关闭 loading 状态。6.3 做一个轻量语义缓存控制成本与重复调用demo 里每调一次接口就计费一次但项目里很多请求是重复的比如用户问了同一个问题两次、或模板化 prompt 只有变量不同。对这类场景我习惯在 API 层之前加一个语义缓存用嵌入向量的相似度判断是否命中。先算 prompt 的向量指纹命中就直接返回缓存文本不发起 API 请求。实现不用很重一个内存字典加一个相似度计算就能应付原型阶段。cache {} def cached_ask(messages, threshold0.96): user_input messages[-1][content] # 简化做法用字符串哈希做精确缓存语义缓存需换成向量相似度 key user_input.strip() if key in cache: return cache[key] result ask_deepseek(messages) cache[key] result return result这段代码是最原始的精确缓存同一个问题重复问会直接走缓存。生产版要加上向量化语义匹配比如把输入 embedding 后算余弦相似度超过阈值视为同一问题。这里要特别提醒缓存键千万要包含 messages 的完整上下文只拿最后一条用户消息做键会导致上下文不同但问题相同的场景误命中给出答非所问的缓存结果。这个坑我踩过后来把 system 消息和用户消息拼在一起算哈希才解决。以上三个技巧做完demo 就已经从“能跑”变成“能上线”。我一直觉得开源 demo 的价值不是拿来直接用而是拿来拆解它背后暴露的完整链路鉴权、参数、流式、异常。每次运行它都要问自己一句如果明天流量翻十倍这个调用方式还能扛住吗答案不能的时候就是该动手改造的时候了。希望这篇笔记能帮你更快走完这条路。本文还有配套的精品资源点击获取