ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DeepSeek从API到本地部署:流式输出与vLLM接入实战指南

DeepSeek从API到本地部署:流式输出与vLLM接入实战指南 简介一份面向零基础学者、进阶用户及专业人士的DeepSeek大模型实操教程以PDF形式系统性讲解关于DeepSeek的完整使用链路解决从陌生到熟练应用的关键问题。全书共六章从准备篇的快速上手、创建AI伙伴到基础对话篇的提问技巧与基础指令再到效率飞跃篇的文档分析、代码生成、复杂任务处理以及场景实战篇的学术论文辅助、自媒体运营、智能学习规划高手进化篇的私人知识库构建、自动化工作流搭建、跨语言切换最后以自我提升篇的自我校正与零基础代码入门收尾。内容覆盖查重降重、标题生成、数据分析、排版优化、学习监督等高频真实场景能让读者按需查阅逐步构建DeepSeek应用能力。资源为单个PDF文档压缩包大小2.78MB目前已有698人学习下载适合作为系统化自学手册或企业内训材料。1. 写在前面DeepSeek 教程为什么总让人卡在半路市面上能搜到的 DeepSeek 教程九成停在网页版聊天这一步打开对话框、输入问题、看它逐字输出然后就没了。可真正把它用起来——写进自己的代码、部署到本地机器、接入编辑器或者业务系统——还隔着 API 鉴权、流式输出、上下文窗口和显存规划这四道坎。这篇笔记按一条能直接复现的落地路径走先用一次真实请求把 DeepSeek API 跑通再实现网页端那种逐字渲染的流式效果然后是本地部署的模型选型与 vLLM 启动参数最后把模型接进编码工具和企业微信这类真实场景。适合刚申请到 key 不知道从哪下手的新手也适合本地部署翻过车的熟手对照排查。2. 从 DeepSeek API 开始先把第一轮对话跑通2.1 网页版聊天和 API 是两回事很多第一次接触 AI 大模型 API 的人会默认网页上能聊那 API 就是发一句话过去、收一句话回来。实际完全不是这样。网页版帮你封装了会话管理、流式渲染、上下文裁剪和失败重试而你拿到 API 之后这些都得自己处理。最直观的区别是API 不记得你上一轮说过什么除非你每次请求都把历史消息重新传一遍。这也是 DeepSeek 这类模型接入工程里最常见的认知门槛——你以为在跟一个“人”对话其实是在跟一个无状态的计算函数对话。从工程角度看这种无状态设计反而是好事。它意味着你可以把 DeepSeek 当成一个可替换的计算单元今天用官方 API明天换成本地部署的 vLLM 服务只要端点地址和模型名变了代码逻辑几乎不用动。所以第一步不是急着写复杂的功能而是把一轮最小对话跑通确认 key、端点、消息格式这三个基础要素没问题。2.2 申请 Key 与连通性测试在 DeepSeek 开放平台注册后创建 API Key这个 key 就是你调用模型的凭证。常见做法是把 key 放在环境变量里而不是硬编码到代码中避免误提交到仓库。DeepSeek 的接口风格与 OpenAI 兼容base_url 填https://api.deepseek.com模型名选deepseek-chat即可。先用 curl 做一次连通性测试这样能最快区分“网络问题”和“代码问题”export DEEPSEEK_API_KEYsk-你的key curl https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -d { model: deepseek-chat, messages: [ {role: system, content: 你是一个只回复JSON的助手}, {role: user, content: 用JSON格式返回当前时间} ], stream: false }如果返回里带choices[0].message.content说明鉴权、网络、模型名三个环节全部通过。这里把stream显式设为false是为了让响应一次性返回便于排查问题。curl 里常见一个坑在 Windows PowerShell 下$DEEPSEEK_API_KEY的引用方式不同会变成传了字面量字符串而不是 key 的值报 401。遇到这种情况先把 key 直接写进-H头里测试确认通了再改回环境变量。2.3 最小 Python 调用非流式对话curl 通了之后可以换到 Python 写正式代码。我用 requests 而不是 openai SDK 做最小示例原因是 SDK 封装太多你反而看不到消息结构。等搞清楚消息是怎么传的再换 SDK 不迟。import os import requests api_key os.environ[DEEPSEEK_API_KEY] url https://api.deepseek.com/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json, } payload { model: deepseek-chat, messages: [ {role: system, content: 你是一个简洁的助手回答不超过50字}, {role: user, content: 用一句话解释什么是上下文窗口}, ], temperature: 0.7, max_tokens: 200, stream: False, } resp requests.post(url, headersheaders, jsonpayload, timeout30) resp.raise_for_status() data resp.json() print(data[choices][0][message][content])这段代码的逻辑很直白构造请求体POST 到接口从返回里取choices[0].message.content。messages是一个列表按时间顺序排列system角色负责设定模型行为user是用户输入。temperature控制随机性值越大回答越发散代码生成类任务我一般调到 0.20.4写作类任务可以放到 0.8 以上。max_tokens是输出上限注意它只限制本轮生成的字数不含输入部分。第一次跑通后把messages里再加一条{role: assistant, content: data[choices][0][message][content]}然后再追加一条新的user消息就能实现多轮对话。所谓“记忆”就是反复把历史消息堆进这个列表——这也是上下文窗口会越用越满的原因。2.4 理解 messages 结构与两个隐形参数上面代码里有一个容易被忽略的点system消息不是摆设。同一个问题把 system 从“你是一个简洁的助手”改成“你是一个喜欢长篇大论的教授”输出长度和风格会明显变化。生产环境里我习惯把业务规则、输出格式约束、安全边界全部写进 system而不是每次在 user 提示词里重复。还有一个参数top_p它和temperature是两种不同的采样策略。temperature调整的是概率分布的“锐度”top_p调整的是候选词集合的“截断范围”。两个同时调容易互相打架我的习惯是只动其中一个需要确定性输出就把temperature调到 0.2 附近top_p保持默认需要创意输出就把temperature调到 0.9top_p调到 0.9 以下。3. 做出网页那种打字机效果SSE 流式输出与中断控制3.1 流式输出为什么是必需品网页端 ChatGPT 那种逐字往外蹦的效果底层是 SSEServer-Sent Events。它不是 WebSocket而是一条普通的 HTTP 响应服务端不停地往同一个连接里写数据块直到完整回答结束。用 SSE 的直观收益是“首字延迟”大幅降低非流式要等模型把几百个 token 全部生成完再一次性返回流式模式下第一个 token 几百毫秒就能到客户端。对工程来说流式不只是体验问题。大模型生成一个长回答可能要几十秒如果走非流式HTTP 连接长时间不返回中间任何一层网关超时都会让整个请求失败而且你拿不到任何中间结果等于一个黑匣子。SSE 方案下即使连接中途断开你手里也保留了已经生成的部分。DeepSeek API 支持stream: true返回体是text/event-stream格式每行一个data:前缀的 JSON 片段。3.2 用 httpx 实现流式请求Python 侧我一般用 httpx 而不是 requests因为 requests 对流式响应的处理比较笨重httpx 原生支持流式读取。下面的代码实现了一个最简的流式对话边接收边打印符合生产环境“先渲染先到内容”的需求import os import json import httpx api_key os.environ[DEEPSEEK_API_KEY] url https://api.deepseek.com/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json, } payload { model: deepseek-chat, messages: [ {role: system, content: 你是一个乐于助人的助手}, {role: user, content: 写一段50字左右的代码生成方案介绍}, ], temperature: 0.7, max_tokens: 500, stream: True, } with httpx.Client(timeoutNone) as client: with client.stream(POST, url, headersheaders, jsonpayload) as resp: for line in resp.iter_lines(): if not line or not line.startswith(data: ): continue data_str line.removeprefix(data: ) if data_str [DONE]: break chunk json.loads(data_str) delta chunk[choices][0][delta].get(content, ) if delta: print(delta, end, flushTrue)逻辑说明分三层client.stream发起流式请求iter_lines按行读取 SSE 数据每一行把data:前缀剥掉之后解析 JSON从delta.content里取增量文本。注意timeoutNone是必须的流式响应会持续很长时间默认超时会在几十秒后掐断连接。[DONE]是 SSE 流的结束标记看到它就该退出循环。这段代码里最容易被新手改坏的地方是removeprefix。如果你用的是 Python 3.8 或更早版本str.removeprefix不存在要改成line[len(data: ):]。另外有些网关会在数据块之间插入空行或多余空格所以代码里做了not line的跳过处理。3.3 前端用 abort 信号止住一次生成服务端流式实现好之后前端就要面对另一个问题用户点“停止生成”怎么让这次请求真正停下来。HTTP 层面没有“取消”语义唯一的办法是客户端主动断开连接。浏览器里对应的就是AbortController热词里常提到的“配合 abort”就是这件事。// 假设 apiUrl、apiKey 已在前面定义 const controller new AbortController(); const stopBtn document.getElementById(stopBtn); async function chat() { const resp await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey}, }, body: JSON.stringify({ model: deepseek-chat, messages: [{ role: user, content: 讲一个200字的技术故事 }], stream: true, }), signal: controller.signal, }); const reader resp.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const text decoder.decode(value, { stream: true }); // 把 text 按 data: 前缀逐行解析后增量渲染 } } stopBtn.onclick () controller.abort();这里的关键是把controller.signal传给 fetch。用户点击停止按钮时调用controller.abort()浏览器会主动断开与服务器的连接。服务端那边会收到连接中断Python 端的iter_lines会抛出异常你的后端要做好捕获并返回“已生成的部分”。还有一个细节controller.abort()只能调用一次如果需要“停止后又重新生成”每次请求都要重新new AbortController()。3.4 流式结束后看 finish_reason流式响应里每个 chunk 都带choices[0].finish_reason只是大部分 chunk 里它是null。只有最后一个 chunk 才会变成stop或者length。这个字段非常重要stop表示模型正常说完length表示达到了max_tokens上限被截断。很多线上问题——比如回答到一半戛然而止——根因就是没看这个字段。# 接上面的流式循环在循环结束后补充判断 reason None # ... 在解析 chunk 时同步记录reason chunk[choices][0].get(finish_reason) if reason length: print(\n[回答被截断请调大 max_tokens 或让用户精简问题])生产环境里我建议把finish_reason透传到前端。前端拿到length时在界面上显示“继续生成”按钮把已经生成的内容拼进 messages 再请求一次。这样比单纯调大max_tokens更省钱因为大部分用户问题不会每次都触到上限。4. 本地部署 DeepSeek模型选型与 vLLM 启动参数4.1 先回答一个问题你的显存能跑多大模型本地部署 AI 大模型之前第一件事不是拉代码而是算显存。模型权重加载进 GPU 显存需要大约参数量乘以 2 字节FP16 精度例如 7B 参数模型大约需要 14GB 显存。但这只是权重的量实际推理还要算上 KV cache 和激活值所以业界惯例是留出 20% 余量。目标硬件可流畅运行的模型规模备注8GB 显卡如 RTX 30702B4B 量化建议 AWQ/GPTQ 4bit 量化版16GB 显卡如 RTX 40807B9B原版 7B 可跑长上下文吃紧24GB 显卡如 RTX 309014B 量化 或 7B 原版兼顾速度与质量48GB 以上 / 多卡32B 及以上建议多卡张量并行DeepSeek 开源模型系列里常见做法是选 R1 蒸馏版或者其他适合本地推理的版本以官方仓库实际发布的权重和显存要求为准。不要看名字带“蒸馏”就以为一定能跑蒸馏只缩小模型大小KV cache 占用依然和上下文长度强相关。max_model_len设 8192 和设 32768显存占用能差好几 GB。4.2 用 vLLM 一条命令起服务本地部署我首选 vLLM不光是速度更因为它自带 OpenAI 兼容端点起完服务之后代码不用改就能从官方 API 切到本地。vLLM 安装完成后一条命令就能起服务vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --served-model-name deepseek-local \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --enforce-eager \ --dtype auto参数逐个说。--served-model-name是给外部调用用的模型别名你可以叫它deepseek-local这样切换环境时不用改代码。--max-model-len限制最大序列长度输入加输出这个值设得越大能支撑的上下文越长但显存占用也越高。--gpu-memory-utilization 0.85表示最多占用 85% 显存留一点给桌面环境或者监控进程。--enforce-eager是给首次部署排障用的它关闭 CUDA graph 优化启动更快但吞吐稍低确认稳定后再去掉默认模式吞吐更高。如果机器上同时有别的服务在吃显存启动 vLLM 时报 CUDA out of memory先查nvidia-smi看显存占用把--gpu-memory-utilization调低到 0.6 再试。部署完成后 vLLM 会默认监听http://localhost:8000/v1/chat/completions就是与 OpenAI 兼容的对话端点。4.3 将现有代码从 API 切到本地上一章写的流式代码要切到本地只需要改两处base_url 和 model 名。DeepSeek 官方 API 的模型名是deepseek-chat本地服务用的是你指定的--served-model-name。把请求地址改成http://localhost:8000/v1/chat/completions即可。# 切到本地部署时只改这两行 url http://localhost:8000/v1/chat/completions # payload 里 model 改为 model deepseek-local # 其余 request、headers、流式解析逻辑完全复用这种兼容设计的好处是你可以先拿官方 API 调通业务逻辑再逐步迁移到本地。迁移后 A/B 对比两者输出差异能帮你判断是“模型差异”还是“业务代码 bug”。我实际验证过同样的 prompts官方 API 和本地同参数模型输出的语气会有细微差别所以接入方别假设“同一个模型名就完全一致”。4.4 Jetson Orin 与低显存设备的部署取舍热词里经常看到“DeepSeek 本地部署 jetson orin”这类 ARM 设备做 AI 推理确实可行但要注意几个特殊点。Jetson Orin 系列用统一内存架构显存和系统内存共享这跟桌面显卡的独立显存不一样。跑模型时要预留系统内存给桌面、驱动和 CUDA 运行时不能把整块内存都塞给模型。在这些设备上我更推荐走 llama.cpp 或 Ollama 路线而不是 vLLM因为 vLLM 对 ARM 统一内存的适配没有 x86 NVIDIA 独立显卡成熟。实际操作上先把模型量化为 4bit GGUF 格式再用 llama.cpp 起服务同样能提供 OpenAI 兼容端点。代价是输出速度比 vLLM 慢不少但如果你的场景是内网工具类问答而不是高并发 API完全够用。部署这类边缘设备时先用短上下文2048做冒烟测试确认服务能起、响应能回再逐步拉长上下文避免一上来就 OOM。提示本地部署最大的隐性成本在维护。CUDA 驱动版本、vLLM 版本、模型文件名任何一个不匹配都可能让服务在启动阶段就崩。建议固定一套经过验证的版本组合不要频繁升级。5. 高频踩坑清单DeepSeek 使用中的 5 个问题与排查5.1 现象报错 “messages tool calls need immediate results”这个问题在 Agent 类项目里几乎必现。你在消息里传入了tool_calls模型据此发起了工具调用但你没有在后续消息里立刻附上工具执行结果而是又发了一个普通用户消息过去模型就报了这个错。原因在于DeepSeek 这类模型在收到工具调用请求后会强制要求下一条消息必须以role: tool的角色返回这个工具的实际执行结果中间不能穿插普通对话。这是模型训练时定下的对话结构约束不是网络问题。解决方式检查你的 Agent 循环。当模型输出finish_reason为tool_calls时立刻执行对应工具然后把结果拼成{role: tool, tool_call_id: 刚才返回的id, content: 结果内容}追加到 messages再发起下一次请求。每次只追加一条 tool 消息不要一次性塞多个结果除非这些结果对应不同的tool_call_id。5.2 现象SSE 流式响应到一半突然断了前端一直转圈表现是页面上的“打字机”效果停了浏览器控制台显示网络请求中断后端日志里看到连接被重置。原因分两类。一类是网络链路问题客户端或中间层有超时配置比如 Nginx 的proxy_read_timeout默认 60 秒大模型生成超过这个时间连接就被掐了。另一类是代码问题前端没有处理流式响应中的[DONE]标记或者后端在生成过程中进程崩溃。解决后端代码里确认每个 chunk 都正确以data:前缀输出Nginx 配置里把proxy_read_timeout调到 300 秒以上前端在catch里区分“用户主动 abort”和“网络异常”前者静默处理后者提示用户重试。还有一个容易忽略的点如果你用了 Gunicorn 等 WSGI 服务器默认 worker 类型不支持 SSE 长连接要换成gunicorn --worker-class gevent或者直接用 FastAPI 的 Uvicorn。5.3 现象同样的提示词本地部署和 API 输出差异很大这个现象在 R1 蒸馏模型上尤其明显。本地部署的模型和官方 API 背后的模型并不是同一个权重输出风格、推理深度、甚至回答正确率都可能不同。很多项目在 API 上测试没问题切到本地后效果断崖式下跌第一反应是“部署出了问题”其实大概率是模型能力差异。解决区分“测试环境”和“生产环境”。如果业务对质量要求高把本地部署用来做开发联调生产流量仍然走官方 API如果必须全部本地化提前准备一批覆盖你业务边界的测试问题做回归对比指标不光是回答正确率还有平均生成长度、是否拒绝回答、是否输出格式错误。5.4 现象上下文一长模型开始答非所问或重复同一段话上下文窗口是个很容易被忽略的隐性限制。你手动拼 messages 时觉得没多少字但 token 不是一个字一个字数的一个汉字通常对应 12 个 token一段代码的 token 消耗远超同样长度的自然语言。当 messages 总 token 数逼近模型上限时模型会出现早期内容遗忘、重复生成、甚至强行拼接的现象。解决在服务端记住每次请求的usage.prompt_tokens在接近模型最大上下文长度时做截断。常见做法是只保留 system 消息、最近几轮对话、以及和当前问题最相关的历史片段丢弃中间的闲聊内容。不要只按角色裁剪要按 token 数裁剪后者才是真实消耗。5.5 现象vLLM 启动几秒后报 CUDA out of memory原因不只是模型太大。--max-model-len设得过大、--gpu-memory-utilization设置过高、同时开多个部署进程都会导致显存不足。有时候你明明用nvidia-smi看到显存够了vLLM 仍然报 OOM那是因为 CUDA context 本身也要占用几百 MB 显存。解决按这个顺序排查——先看有没有残留的 Python 进程占着显存fuser -v /dev/nvidia*找到并结束再把--gpu-memory-utilization降到 0.5 做冒烟测试最后把--max-model-len降到 4096 对比显存占用。如果这三个都试过还是 OOM说明卡真的不够去选更小的模型或者量化版本。6. 把 DeepSeek 接进编码工具与业务系统三个进阶玩法6.1 Codex 接入 DeepSeek只改一个配置文件Codex CLI 是 OpenAI 开源的终端编码助手它支持通过配置自定义模型提供商。DeepSeek 的 API 与 OpenAI 兼容所以接入方式就是把 OpenAI 端点换成 DeepSeek 的。找到 Codex CLI 的配置文件常见位置是~/.codex/config.toml添加一个自定义 providermodel_provider deepseek [model_providers.deepseek] name DeepSeek base_url https://api.deepseek.com/v1 env_key DEEPSEEK_API_KEY配置完成后在项目目录运行codex它会用 DeepSeek 模型接管代码生成和 Shell 命令解释。这里有一个值得记住的坑Codex 这类 Agent 工具会频繁发起工具调用而 DeepSeek 的工具调用要求“立即返回结果”如果你的网络到 API 的时延偏高Agent 循环会频繁报错。这种情况下打开配置文件里的调试日志观察每次tool_call_id是否回传一致。6.2 企业微信里跑一个 DeepSeek 机器人把 DeepSeek 接进企业微信最省事的方式是群机器人 webhook。webhook 只能主动推送消息不能接收用户回复要实现“在群里 机器人提问”就需要自建应用接收回调。回调服务本身不复杂一个 FastAPI 接口接收企业微信 POST 过来的消息解析出文本内容转发给 DeepSeek再把回答 POST 回群机器人 webhook。# 伪代码企业微信回调 - DeepSeek - 群机器人回复 from fastapi import FastAPI, Request import requests app FastAPI() app.post(/wechat/callback) async def handle_msg(req: Request): data await req.json() user_text data.get(text, {}).get(content, ) # 调用 DeepSeek 获取回复 answer deepseek_chat(user_text) # 推送回企业微信群机器人 webhook https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的key requests.post(webhook, json{msgtype: text, text: {content: answer}}) return {errcode: 0}这个玩意的核心价值不是“做一个聊天机器人”而是把内部知识库、告警信息、日常文档通过提示词工程丢给 DeepSeek变成一个能回答惯例问题的内部助手。踩坑提示企业微信回调要求 5 秒内响应否则会重试所以 DeepSeek 调用一定要走异步先把errcode: 0返回给企业微信再慢慢生成回答去推送。6.3 上线前必做的三个验证不管接入哪个场景我建议上线前跑一组固定的验证用例别拿“能聊两句”当通过。测量三个指标就够起步首 token 延迟TTFT、平均生成速度TPS、上下文截断后的行为。验证项方法通过标准首 token 延迟用流式请求计从发出到第一段 content 的时间3 秒以内算健康生成速度记录完整回答的 token 数与耗时个人使用 10 TPS 即可上下文截断把 messages 塞到接近上限再提问不报错、能正常回答或明确拒绝这三项跑完你才算真正“精通”了 DeepSeek 接入不是背下了多少个参数而是知道一次回答是从端点到模型的哪一段链路里出来的。我的习惯是每换一个环境新机器、新模型版本、新网络都先跑一遍这三个验证省掉后面大量定位时间。接入 AI 大模型这件事让我最受益的一个工作习惯是永远先写最小冒烟测试再写业务代码。一个能稳定重放的 20 行脚本比什么调试工具都管用。从 DeepSeek 开始这套方法论可以平移到任何 OpenAI 兼容的模型服务上——今天换 Qing 或者别的国产模型代码改动量也就那么几行。希望这些踩过坑的经验能帮你在自己的接入路上少走一段弯路。本文还有配套的精品资源点击获取
返回列表