ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DeepSeek 实战:API调用、本地部署与生产避坑

DeepSeek 实战:API调用、本地部署与生产避坑

简介:《DeepSeek从入门到精通》由清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室团队编写,面向希望系统了解并上手DeepSeek的开发者、内容创作者与AI学习者,重点解决模型认知、任务匹配和提示语设计等问题。压缩包内为1个PDF文件,约4.14MB,内容围绕DeepSeek是什么、能做什么、如何使用及如何进阶展开,涵盖智能对话、文本生成、语义理解、代码生成补全、文件与图片文字读取等应用场景。文档还对比推理模型与通用模型在数学推导、逻辑分析、创意写作等任务上的差异,说明提示语策略、模型选择原则及常见误区,并给出任务需求与提示语示例。读者可据此判断何时选用推理模型、何时选用通用模型,并借助提示语原则优化提问与复杂任务拆解,提升文本创作、代码生成和知识分析效率。已有335人学习,适合按章节查漏补缺。

1. DeepSeek 到底是什么:先把三类模型和两种用法分清楚

上周有个做客服系统的同行问我,DeepSeek 是不是就是那个"便宜的大模型",把接口地址一换就能顶上去。我让他先别急着改代码——他手里的活儿是工单分类加多轮追问,属于典型的"结构化输出 + 长上下文"任务,而他准备直接上带思维链的推理模型。真按这个方案上线,JSON 解析会大面积失败,多轮对话还会报 400。所以讲 DeepSeek 从入门到精通,第一件要分清的事不是参数怎么调,而是它到底给了你几样东西:一个是通用对话模型,负责日常问答、抽取、改写;一个是带显式思维链的推理模型,负责数学、逻辑、复杂代码;还有一批从 1.5B 到 70B 不等、可以下载权重的小尺寸蒸馏模型,负责私有化和离线场景。这三类模型的能力边界、参数支持、价格结构都不一样,混着用就是给自己挖坑。下面按我自己的落地顺序拆:先跑通最小调用,再处理提示词和推理链,然后接检索和工具调用,最后把上线后真踩过的坑摊开。

2. 第一次跑通:API 调用和本地推理,两条路的取舍点在哪

选 API 还是本地部署,判断标准其实只有三条:数据能不能出内网、并发峰值有多高、单次调用的上下文有多长。数据敏感、必须过等保、或者要保证离线可用,那只能本地;反过来,如果只是想验证效果、做原型、跑内部工具,API 是唯一理性选择,因为本地部署一台能跑得动大参数量模型的机器,光显存成本就够买好几年的调用量了。中间地带是存在的——用蒸馏小模型在单卡上跑,牺牲一部分推理质量换成本和可控性,这也是后一章要重点算的账。

2.1 API 方式:兼容 OpenAI 协议的最小可用调用

DeepSeek 的接口走的是 OpenAI 兼容协议,这意味着你现有的 SDK、现成的封装库、甚至团队里写了一半的代码,基本不用重写,改 key 和 base_url 就能跑。这是它上手成本低的主要原因,也是最容易被误读的地方——协议兼容不等于行为兼容,采样参数的默认值、流式返回的字段结构、错误码的含义,都有差异。

from openai import OpenAI client = OpenAI( api_key="YOUR_API_KEY", base_url="https://api.deepseek.com", # OpenAI 兼容入口,注意末尾不要多写 /v1 ) resp = client.chat.completions.create( model="deepseek-chat", # 通用对话模型;推理模型换成 deepseek-reasoner messages=[ {"role": "system", "content": "你是工单分类助手,只输出规定格式,不解释。"}, {"role": "user", "content": "用户反馈:付款成功但订单状态还是待支付,已重试两次。"}, ], temperature=0.2, # 分类任务压低随机性,取值理由见 2.3 max_tokens=512, # 输出上限,按最长可能答案的 1.5 倍设 stream=False, # 先跑通再考虑流式,调试阶段关掉 timeout=60, # 必设,默认超时在长文本场景下太短 ) print(resp.choices[0].message.content) print(resp.usage) # 每次都要看:prompt_tokens / completion_tokens

这段代码里真正需要注意的只有三处。base_url不要自作聪明补成/v1结尾,部分兼容层会因此拼出重复路径,报 404;timeout必须显式设置,长上下文加推理模型时单次响应超过 60 秒是常事,不设超时会让上游连接池被拖死;resp.usage要打印出来,它是后面算成本和做容量规划的原始数据,很多人上线两个月了还不知道自己平均每次调用花多少 token,这是血泪经验。

对于推理模型,返回体里会多一个reasoning_content字段,装着模型的思考过程,content里才是最终答案。这两个字段的关系处理错了,就是 5.1 节要展开的那个 400 报错。

2.2 本地起服务:Ollama 试水和 vLLM 上生产是两回事

本地部署最容易犯的错,是拿试水工具直接上生产。Ollama 适合验证"这个尺寸的模型在中文任务上够不够用",一条命令就能跑起来;vLLM 适合承接真实并发,有连续批处理和 PagedAttention,吞吐量差一个数量级。两者的部署命令和关键参数完全不是一套。

先用 Ollama 做效果验证,五分钟能看到结果:

# 拉取蒸馏版小模型,具体 tag 以模型仓库页面为准,这里以 7B 为例 ollama pull deepseek-r1:7b # 启动本地服务,默认监听 11434 ollama serve # 单条测试,确认模型能正常输出中文 ollama run deepseek-r1:7b "用三句话说明 MoE 架构相比稠密模型的显存优势"

验证通过之后,换成 vLLM 起 OpenAI 兼容服务,业务代码只需要改 base_url 指向本机:

python -m vllm.entrypoints.openai.api_server \ --model /models/DeepSeek-R1-Distill-Qwen-7B \ --served-model-name ds-local \ # 业务侧 model 字段填这个名字 --tensor-parallel-size 1 \ # 等于使用的 GPU 张数,8 卡就填 8 --max-model-len 8192 \ # 上下文上限,直接影响 KV Cache 显存占用 --gpu-memory-utilization 0.90 \ # 预留 10% 给 CUDA 上下文和临时张量 --port 8000

--tensor-parallel-size必须整除注意力头数,填错会直接启动失败;--max-model-len是最容易翻车的参数,它决定了每个并发请求预分配的 KV Cache 大小,设成 32768 而实际业务只需要 8K,等于白白浪费一半显存,进而拉低并发数;--gpu-memory-utilization建议从 0.85 起试,0.95 看起来很划算,但留给临时张量的空间不足时会在长请求上偶发 OOM,属于典型的玄学故障,日志里只有一行 CUDA out of memory,看不出规律。

显存估算的经验公式是:权重显存约等于参数量乘以精度字节数(BF16 下 7B 模型约 14GB),再加上 KV Cache 和激活值的开销,实际占用通常是权重的一点三到一点五倍。按 7B 模型、8K 上下文、10 并发估算,24GB 单卡是起步配置,32GB 更稳。

2.3 五个必调参数和它们的实际取值

参数不是调得越多越好,生产环境里真正需要动的就五个。把这张表当成默认配置来改,比每次凭感觉试要靠谱得多。

参数作用通用对话模型推理模型说明
temperature采样随机性0.2~0.7不支持抽取、分类、JSON 输出取 0.2 以下
top_p核采样阈值0.9~0.95不支持与 temperature 只调一个,避免叠加
max_tokens输出长度上限按业务设需留足思考空间推理模型要把思考 token 算进去
stream流式返回前端交互场景开同左关闭时注意超时设置
response_format结构化输出json_object同左需配套提示词约束

temperature和top_p在推理模型上设置了也不会生效,部分兼容层甚至会因为传了这些字段抛参数校验错误。这不是 bug,是推理模型的解码策略本身就固定了,它靠强化学习收敛出来的思维链不需要你去搅动分布。把这两个字段从推理模型的调用里删掉,能省掉一类莫名其妙的报错。

max_tokens在推理模型上是双重含义:思考过程也消耗这个配额。设成 1024 让模型解一道需要深度推理的题,很可能思考到一半就被截断,content返回空字符串,而finish_reason是length。这属于现象诡异但原因简单的一类问题,排查时第一时间看finish_reason。

3. 提示词与思维链:通用模型和推理模型不能用同一套写法

网上流传的提示词模板,绝大多数是按通用对话模型的脾气写的——给角色、给示例、给分步指令,甚至专门叮嘱"请一步一步思考"。这套东西搬到推理模型上,效果往往不升反降。原因是推理模型已经通过强化学习内化了推理行为,你再用提示词灌一遍思维链,等于在它自己的思考过程外面套了一层壳,既浪费 token 又干扰收敛。这一章把两类模型的写法分开讲。

3.1 通用对话模型的提示词骨架:四段式最稳

我在生产里用的骨架固定四段:角色与边界、任务定义、约束条件、输出格式。四段缺一段就会出问题——缺边界,模型会主动扩展话题;缺约束,它会在答案里加"以上仅供参考"这类废话;缺输出格式,下游解析就要写一堆容错。

prompt = """[角色] 你是电商售后工单分类器,只处理与订单、物流、退款相关的问题。 [任务] 将用户输入归入以下四类之一:order_status / logistics / refund / other。 [约束] 1. 只输出 JSON,不要任何解释性文字; 2. 无法判断时归入 other,不要猜测; 3. 分类依据只看用户明确陈述的事实,不推断。 [输出格式] {"category": "四选一的枚举值", "confidence": 0.0到1.0之间的小数} """

这套写法的关键在第三段。约束要写成可判定的规则,而不是形容词。"回答要专业"这种约束等于没写,"只输出 JSON,不要任何解释性文字"才是可执行的。实测下来,把约束写成编号列表,比写成一段话的遵从率高不少,模型对编号结构更敏感。

3.2 推理模型的三个反直觉用法

第一,不要在提示词里写"请一步一步思考""Let's think step by step"。这句话对通用模型是有效增益,对推理模型是纯噪声,它会让模型在已经内化的思维链之外,再生成一段模仿人类的"步骤说明",输出变长、延迟上升、准确率反而可能下降。直接给问题和约束就够了。

第二,少样本示例要慎用。通用模型靠示例对齐格式,推理模型对示例的敏感性更高,给三个示例它容易把示例里的具体数值当成模式去套。如果必须给格式参考,给一个就够,并且把示例里的业务数据换成明显无关的占位内容。

第三,追问要开新会话而不是追加历史。推理模型的多轮对话里,上一轮的思考过程不应该带回下一轮的上下文。正确做法是只保留content,把reasoning_content丢掉。这一点在 5.1 节展开。

3.3 结构化输出:让 JSON 稳定解析的写法

即便开了 JSON 模式,返回空对象、字段名拼错、类型不符的情况仍然会出现。原因是 JSON 模式只保证"输出是合法 JSON",不保证"符合你的 schema"。要压住这个不确定性,三件事一起做:开启response_format、提示词里出现 json 字样并给出完整示例、代码侧做 schema 校验加一次重试。

import json def extract(user_text: str) -> dict: resp = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "输出必须是 JSON 对象,键名固定为 category 和 confidence。"}, {"role": "user", "content": user_text}, ], temperature=0.0, # 结构化抽取直接归零 response_format={"type": "json_object"}, # 强制 JSON 模式 max_tokens=256, ) raw = resp.choices[0].message.content try: data = json.loads(raw) assert data.get("category") in {"order_status", "logistics", "refund", "other"} return data except (json.JSONDecodeError, AssertionError): # 兜底:一次重试,把错误结构回传给模型让它自纠 return {"category": "other", "confidence": 0.0}

提示词里必须显式出现 "JSON" 这个词,这是 JSON 模式的硬性前提,漏了会返回空白内容,而且不报错,属于最难排查的一类。temperature=0.0在这里不是玄学,是为了让同一个输入多次调用得到一致结果,便于回归测试。最后那段except是后悔药——线上跑久了,一定会遇到模型返回{"category": "退款"}这种中文枚举值的情况,兜底分支保证下游不会拿到脏数据。

4. 接进业务系统:检索增强、工具调用与 token 成本

单次调用跑通只是起点。真实业务里模型的输入从来不是用户那一句话,而是检索出来的文档片段、数据库查询结果、历史会话摘要拼起来的一大段上下文。这一段决定了系统的上限,也决定了账单的下限。

4.1 文档切分与检索链路的最小实现

切分策略比 embedding 模型的选择更影响最终效果。我踩过的坑是按固定字符数硬切,切出来的块经常在句子中间断开,模型拿到半句话只能瞎猜。改成按标题层级切、块内保留父级标题、块大小控制在 300 到 500 字、相邻块重叠 60 字,召回质量立刻上一个台阶。

def build_prompt(question: str, chunks: list[str]) -> str: # 每块前面挂上来源编号,方便模型引用,也方便你回溯是哪块召错了 context = "\n\n".join(f"[{i+1}] {c}" for i, c in enumerate(chunks)) return f"""根据下列资料回答问题,资料中没有的信息直接说"资料未提及",不要编造。 引用时标注来源编号,格式如 [1]。 资料: {context} 问题:{question} """

"资料未提及"这句约束是整段提示词里最值钱的部分。不加它,模型会用训练时的先验知识补全答案,用户看到的是一段读起来很顺、但和你的文档毫无关系的内容,这种错误极难被人工发现。"标注来源编号"则给了你一条排查线索:答错了先看引用的编号对不对,能快速区分是检索层的问题还是生成层的问题。

4.2 函数调用:让模型去查数据而不是背数据

凡是会变的数据——库存、订单状态、实时价格——都不要指望模型记住,用工具调用让它现查。DeepSeek 的通用对话模型支持标准 OpenAI 格式的 tools 参数,写法可以直接复用。

tools = [{ "type": "function", "function": { "name": "query_order", "description": "根据订单号查询订单当前状态,仅在用户提供订单号时调用", "parameters": { "type": "object", "properties": {"order_id": {"type": "string", "description": "纯数字订单号"}}, "required": ["order_id"], }, }, }] resp = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": "我的订单 20240512001 到哪了"}], tools=tools, tool_choice="auto", # 改成 {"type":"function","function":{"name":"query_order"}} 可强制调用 )

description里那句"仅在用户提供订单号时调用"是必须的。不写这句,模型会在用户只是问"一般多久发货"的时候也去调接口,然后因为参数缺失反复重试,把一次简单问答变成三次调用。函数调用失败最常见的原因不是模型不会调,而是它太爱调。tool_choice="auto"是默认值,某些强约束场景可以强制指定,但要清楚强制的代价是模型失去判断"该不该调"的能力。

拿到工具返回结果后,要把结果作为role="tool"的消息追加进 messages 再请求一次,模型才会基于结果生成自然语言回答。这一步漏掉,用户会看到一段空的content,只因为模型还在等工具结果。

4.3 成本与延迟:把 token 账算在写代码之前

成本结构上有两件事值得提前规划。一是输入输出分开计价,输出通常贵得多,所以能用结构化短输出解决的,不要让它写长篇;二是上下文缓存,同一段长系统提示词或同一份长文档在短时间内反复出现时,命中的部分输入有折扣,把固定内容放前面、变化内容放后面,命中率会明显提升。

延迟方面,首 token 时间和总时长要分开看。首 token 取决于输入长度和排队,总时长还取决于输出 token 数。给用户做交互界面时优先保证首 token 快,也就是把输入压短;做批处理任务时优先保证总时长可控,也就是限制max_tokens。这两个目标经常冲突,得看场景取舍,不能一概而论。

5. 避坑与排查:上线后最容易翻车的五个地方

下面五条都是真出过问题的,按"现象、原因、解决"写,遇到类似症状可以直接对照。

5.1 多轮对话把 reasoning_content 回传,接口直接 400

现象:单轮调用完全正常,改成多轮之后第二轮开始稳定报 400,错误信息指向消息格式非法。

原因:推理模型的返回体里reasoning_content和content是两个字段,很多人图省事把整个message对象原样 append 进历史记录,下一轮请求里就带上了reasoning_content。这个字段不允许作为输入回传。

解决:构造历史消息时只保留role和content两个键,显式重建字典而不是直接复用返回对象。加一个to_history(msg)的转换函数,所有地方统一走它,别在每个调用点手写。

5.2 调高温度想要"更有创意",结果 JSON 全线解析失败

现象:换了temperature=1.2之后,下游日志里每隔几条就出现 JSON 解析异常,人工看返回内容还能看出是模型加了注释或者少了引号。

原因:结构化输出任务对采样随机性极其敏感,温度越高,模型越倾向于"自由发挥"格式细节。JSON 模式只保证语法合法,不保证严格遵守你的 schema。

解决:所有需要机器解析的调用,temperature一律压到 0.2 以下,实测 0.0 到 0.1 最稳。"创意"类需求单独开一条链路,和抽取、分类的链路物理隔离,不要用同一个配置。

5.3 长文档整篇塞进去,超出上下文后行为不一致

现象:短文正常,超过一定长度后要么报长度超限,要么不报错但答案明显漏掉了后半部分内容。

原因:不同模型版本的上下文窗口不一样,超出之后的行为也不统一——有的直接报错,有的静默截断。而截断是从哪一端截,取决于服务端实现,你无法假设它保留了开头。

解决:在客户端做硬性长度校验,用 tokenizer 先估算再发送,超过阈值就走分块摘要或者检索。不要依赖服务端兜底,静默截断是最难查的一类问题,因为它不报错。

5.4 流式输出中途断连,用户看到半句话就没了

现象:前端偶发显示不完整的回答,刷新后又正常,日志里没有任何异常。

原因:流式返回是长连接,网关空闲超时、客户端网络抖动、或者服务端限流都可能在传输中途断开,而很多 SDK 默认不重试流式请求。

解决:在客户端捕获流读取异常,记录已经收到的内容长度,重连时把已收到的部分作为上下文让模型续写,或者干脆降级成非流式重试一次。同时检查网关的空闲超时配置,调到大于最长预期响应时间。

5.5 本地部署按参数量乘二估算显存,一上并发就 OOM

现象:单条请求跑得好好的,压测到十几个并发就开始报显存不足,重启后又能跑一会儿。

原因:显存不是只装权重。KV Cache 随着并发数和上下文长度线性增长,激活值和临时张量也要占一块。只按权重估算,等于把最可变的那部分完全忽略了。

解决:先用单并发跑出稳定基线,再逐步加压找到拐点;--max-model-len按业务实际需要的长度设,不要图省事设成模型支持的最大值;--gpu-memory-utilization留出至少 10% 余量。这三条调完还顶不住,就该考虑量化或者加卡了。

6. 进阶:蒸馏小模型和 API 到底怎么选,用一张评测表定

选型讨论最容易变成口水战,因为大家比的是"感觉哪个更聪明"。我的做法是先花半天时间搭一个二十到五十条的小评测集,全部来自真实业务样本,标注好标准答案,然后让候选方案跑同一批题,人工或脚本打分。这件事的投入产出比高得离谱,但愿意做的人不多。

评测集按任务类型分层,每层至少十条,覆盖抽取、分类、多跳问答、长文档理解。打分只看两个指标:准确率和单次成本。延迟单独记录,因为它和并发策略强相关,不适合和准确率放在一起比。

方案抽取准确率多跳问答单次成本适用判断
通用对话模型 API高中中结构化任务主力
推理模型 API中高高复杂推理、代码场景
7B 蒸馏模型本地中低极低高频简单任务、离线场景
32B 蒸馏模型本地较高中低数据不出内网的折中方案

跑完这张表通常会得到一个反直觉的结论:最高频的那部分流量,用最小的模型就够了,把大模型留给真正难的那百分之十。我现在的做法是路由分流——先用规则和小模型处理,置信度低于阈值再升级到大模型,整体成本能降下来一大截,而用户几乎感知不到差别。

评测脚本本身很简单,关键是别偷懒:

import json, time def evaluate(cases: list[dict], call_fn) -> dict: hit, total_cost, total_latency = 0, 0.0, 0.0 for c in cases: t0 = time.time() out = call_fn(c["input"]) # call_fn 内部区分走 API 还是本地服务 total_latency += time.time() - t0 if normalize(out) == normalize(c["answer"]): hit += 1 n = len(cases) return {"accuracy": hit / n, "avg_latency": total_latency / n}

normalize这个函数别省,中文标点全半角、数字格式、大小写都会让"其实答对了"被判成错,我第一次跑评测就是因为这个把准确率算低了两成,差点错误地把一个可用方案毙掉。

最后一句实在话:模型选型这件事,讨论三天不如跑一次评测。我现在的习惯是任何新方案进生产前必须先过评测集,哪怕只跑二十条,也比拍脑袋强。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进