简介:这份PDF资料面向对人工智能与大语言模型感兴趣的开发者、研究人员及入门用户,系统讲解DeepSeek的技术架构与实战应用。内容从混合专家(MoE)模型原理切入,对比ChatGPT等主流工具在多语言处理、编程辅助与推理成本上的差异,并覆盖注册登录、界面操作、文本生成、代码调试等基础与进阶场景,还给出API集成步骤、网络故障排除与技术疑难解答方案,兼顾企业级成本效益分析。资源包为1个PDF文件,约770KB,结构紧凑便于通读与检索。目前已有7644人学习下载,适合希望快速建立DeepSeek认知、完成从概念到落地实践的读者,可据此掌握模型选型思路、开发集成方法与常见问题应对策略。
1. 从「会聊天」到「能干活」:大语言模型落地到底卡在哪
很多人第一次接触 DeepSeek 这类大语言模型,都是从对话框里敲一句「帮我写个周报」开始的。聊得挺顺,于是信心满满地想把它接进自己的业务系统——结果第一周就翻车:接口调通了,但输出格式飘忽不定;换个问法,同一个问题答案能差出十万八千里;想让它读一份内部文档,它张口就开始编。问题不在模型本身,而在于「会聊天」和「能干活」之间隔着一整套工程方法。
这篇笔记要讲清楚的,就是这套方法:大语言模型的技术底座是什么、推理和微调怎么选、提示词怎么写成可复用的模板、检索增强怎么搭、参数怎么调、坑在哪。适合两类人——刚拿到 API Key 想跑通第一个 Demo 的新手,以及已经把模型接进系统、但被幻觉和格式问题折磨的熟手。全程不依赖任何特定厂商的独家功能,思路可以迁移到同类模型上。
2. 先搞懂模型在干什么:Transformer、Token 与推理参数
2.1 自回归生成:为什么同一个问题问两遍答案不一样
大语言模型的本质是一个「下一个 Token 预测器」。你给它一段文本,它输出词表里每个 Token 作为下一个位置的概率分布,然后按某种策略挑一个出来,拼回输入,再预测下一个,循环往复直到遇到结束符或达到长度上限。这个机制决定了两件事:第一,模型没有「查表」能力,它输出的每个字都是当场算出来的概率采样;第二,只要采样过程带随机性,同样的输入就可能得到不同输出。
理解这一点,后面所有参数调优才有依据。温度(temperature)控制的是概率分布的平滑程度:温度趋近 0 时,模型几乎总选概率最高的那个 Token,输出稳定但可能呆板;温度调高,低概率 Token 也有机会被选中,输出多样但容易跑偏。Top-p(核采样)则是从累积概率达到 p 的最小 Token 集合里采样,相当于动态截断长尾。实践中我一般这么配:
| 场景 | temperature | top_p | 说明 |
|---|---|---|---|
| 结构化抽取、代码生成 | 0 ~ 0.2 | 0.1 ~ 0.5 | 要稳定,别让它自由发挥 |
| 文案润色、头脑风暴 | 0.7 ~ 1.0 | 0.8 ~ 0.95 | 要多样性,允许意外 |
| 多轮对话 | 0.5 ~ 0.7 | 0.7 ~ 0.9 | 平衡稳定与自然 |
还有一个容易被忽略的参数是最大生成长度(max_tokens)。设太小,回答被硬生生截断;设太大,遇到模型「刹不住车」时会白烧算力。我的习惯是按任务预估:抽取类任务给 256 到 512,长文生成给 2048 起步,同时配合停止符(stop)让模型在指定标记处主动收尾。
2.2 Token 计费与上下文窗口:钱和记忆都花在哪
Token 是模型处理文本的最小单位,中文里一个汉字通常对应 1 到 2 个 Token,英文一个单词约 1.3 个 Token。这直接关系到两件事:成本和上下文窗口。上下文窗口是模型一次能「看见」的 Token 总数,包括你给的输入和它生成的输出。窗口再大也不是免费的——输入越长,首 Token 延迟越高,费用越贵。
一个常见的误区是把整本手册塞进上下文,指望模型自己找答案。实测下来,当输入超过窗口的 60% 到 70% 时,模型对中间位置信息的召回率会明显下降,这就是所谓的「中间迷失」。正确做法是先用检索把最相关的几段捞出来,再喂给模型,而不是无脑堆料。
# 估算一段中文文本的 Token 数(粗略版,用于成本预估) def estimate_tokens(text: str) -> int: # 中文按 1.5 Token/字估算,英文按 1.3 Token/词估算 chinese_chars = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff') english_words = len([w for w in text.split() if w.isascii()]) return int(chinese_chars * 1.5 + english_words * 1.3) sample = "请把下面这段合同条款抽取成 JSON,字段包括甲方、乙方、金额、期限。" print(estimate_tokens(sample)) # 用于判断是否接近窗口上限这段代码不追求精确,目的是在拼接 Prompt 前快速判断「这坨料会不会撑爆窗口」。参数上,中文系数取 1.5 是偏保守的估计,实际不同分词器会有差异,宁可高估不要低估。
2.3 推理与微调:什么时候该动权重,什么时候只动提示词
很多人一遇到效果不好就想微调,这是典型的用力过猛。判断标准很简单:如果问题是「模型不知道你的业务规则」,优先用提示词和检索增强;如果问题是「模型不懂你的领域语言风格或输出格式极其固定」,才考虑微调。
微调分全参数微调和参数高效微调(如 LoRA)。全参数微调动辄需要多卡,成本高、周期长;LoRA 只训练一小部分低秩矩阵,单卡就能跑,适合大多数业务场景。但微调有个硬门槛:你得有几百到几千条高质量标注样本,而且样本质量比数量重要得多。我见过用 200 条脏数据微调出来的模型,效果还不如好好写提示词。
提示:在动微调之前,先把提示词工程和检索增强做到位。这两样能解决 80% 的效果问题,且迭代成本几乎为零。
3. 把提示词写成可复用的模板:从随手问到结构化输出
3.1 角色、任务、约束、示例:四段式模板
随手写的提示词最大的问题是不可复现。今天调通了,明天换个输入就崩。解决办法是把它写成固定结构的模板。我常用的是四段式:角色设定、任务描述、约束条件、输出示例。
角色设定不是玄学,它影响的是模型激活哪部分知识。写「你是一名资深合同审核员」和写「你是一个助手」,模型对同一段条款的敏感度确实不同。任务描述要具体到动作和对象,别写「处理一下这段文本」,要写「从下面文本中抽取甲方名称、乙方名称、合同金额、生效日期四个字段」。约束条件负责堵住模型自由发挥的口子,比如「金额只保留数字,不带单位」「日期统一格式为 YYYY-MM-DD」「找不到的字段填 null,不要编造」。输出示例则是最强的格式锚点,给一个完整的 JSON 样例,模型照葫芦画瓢的准确率会大幅提升。
PROMPT_TEMPLATE = """你是一名合同信息抽取专员。 任务:从下面的合同文本中抽取四个字段。 约束: 1. 金额只保留阿拉伯数字,不带货币符号和单位; 2. 日期统一为 YYYY-MM-DD 格式; 3. 任何字段在原文中找不到时填 null,禁止推测; 4. 只输出 JSON,不要任何解释文字。 输出示例: {{"party_a": "某某公司", "party_b": "某某公司", "amount": 100000, "effective_date": "2024-01-01"}} 合同文本: {contract_text} """ def build_prompt(contract_text: str) -> str: # 用 format 注入变量,注意 JSON 示例里的花括号要转义 return PROMPT_TEMPLATE.format(contract_text=contract_text)这里的关键点是「禁止推测」和「只输出 JSON」这两条约束。前者压制幻觉,后者省去后处理时正则匹配的麻烦。参数上,{contract_text}是唯一的变量槽,模板其余部分保持稳定,这样每次调用的行为才可预期。
3.2 少样本示例怎么选:数量、顺序与边界案例
少样本(few-shot)示例不是越多越好。经验值是 2 到 5 个,超过 5 个收益递减,还白烧 Token。选示例有三个原则:覆盖不同类别、包含边界情况、顺序从简到繁。
比如做情感分类,别给三个全是正面的例子,要给一个正面、一个负面、一个中性。边界案例尤其重要——像「还行吧」这种模棱两可的输入,给一个标注为「中性」的示例,模型遇到类似情况就不会乱猜。顺序上,把最典型的放前面,最刁钻的放最后,因为模型对靠近输入的示例印象更深。
还有一个细节:示例的格式必须和期望输出完全一致。你示例里写的是"label": "positive",就别指望模型输出"情感": "正面"。格式一致性是少样本学习里最容易被忽视的坑。
3.3 输出格式约束:JSON Schema 与后处理兜底
光靠提示词约束格式,偶尔还是会翻车——模型可能多写一句「好的,以下是结果」,或者在 JSON 外面套一层 Markdown 代码块。稳妥的做法是双保险:提示词里明确要求纯 JSON,代码里再做一层解析兜底。
import json import re def parse_model_output(raw: str) -> dict: # 第一层:直接尝试解析 try: return json.loads(raw) except json.JSONDecodeError: pass # 第二层:剥掉 Markdown 代码块标记后再试 cleaned = re.sub(r"^```(?:json)?\s*|\s*```$", "", raw.strip()) try: return json.loads(cleaned) except json.JSONDecodeError: pass # 第三层:用正则抓第一个完整的大括号块 match = re.search(r"\{.*\}", raw, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass # 全部失败,返回带错误标记的结果,交给上层决定重试还是人工介入 return {"_parse_error": True, "raw": raw}这个三层兜底逻辑是我踩了无数次坑之后固定下来的。第一层处理理想情况,第二层处理模型爱加代码块的毛病,第三层处理模型在 JSON 前后啰嗦了几句的情况。全部失败时不要抛异常直接崩,而是返回带错误标记的结构,让调用方决定是重试、降级还是转人工。参数上,正则里的re.DOTALL让.能匹配换行符,否则跨行的 JSON 抓不全。
4. 检索增强生成:让模型读你的文档而不是编
4.1 文档切分:按语义切还是按字数切
检索增强(RAG)的核心思路是:把文档切成小块,存进向量库,用户提问时先检索最相关的几块,拼进提示词让模型基于这些内容回答。第一步就是切分,而切分质量直接决定检索质量。
按固定字数切是最省事的做法,但会把一句话拦腰截断,检索出来的片段语义不完整。更好的做法是按语义边界切:优先在段落、标题、句号处断开,同时设一个最大长度上限。我一般用「递归字符切分」的思路——先按双换行切,块还太大就按单换行切,再大就按句号切,直到每块落在 300 到 800 字之间。
块大小是个权衡:太小,单块信息不足,模型答不全;太大,检索精度下降,还挤占上下文。中文场景下 500 字左右是个不错的起点,再根据实际召回效果微调。块之间留 10% 到 20% 的重叠,能缓解边界信息丢失的问题。
4.2 向量化与检索:相似度阈值怎么定
切好的块要用嵌入模型转成向量存起来。检索时把用户问题也转成向量,算余弦相似度,取最相似的 Top-K 块。这里有两个参数要调:Top-K 和相似度阈值。
Top-K 一般取 3 到 5。取太少可能漏掉关键信息,取太多会引入噪声,还可能触发前面说的「中间迷失」。相似度阈值是过滤低质量召回的闸门,低于阈值的块直接丢弃。阈值定多少取决于你的嵌入模型和语料,没有万能值。我的做法是先跑一批测试问题,看正确块和无关块的相似度分布,把阈值卡在两者之间。
import numpy as np def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float: # 余弦相似度,值域 [-1, 1],越接近 1 越相似 return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))) def retrieve(query_vec, doc_vecs, docs, top_k=4, threshold=0.35): scored = [(cosine_similarity(query_vec, dv), d) for dv, d in zip(doc_vecs, docs)] # 先按相似度降序,再过滤掉低于阈值的 scored.sort(key=lambda x: x[0], reverse=True) filtered = [(s, d) for s, d in scored if s >= threshold] return filtered[:top_k]阈值 0.35 只是个起点,不同嵌入模型的相似度尺度不一样,必须用自己的数据校准。如果发现经常召回为空,说明阈值偏高;如果召回里混了一堆不相关的,说明阈值偏低或切分有问题。
4.3 拼装上下文:把检索结果塞进提示词的顺序
检索回来的块怎么拼进提示词,也有讲究。我的做法是给每块编号,并在提示词里明确要求「只依据以下资料回答,资料中没有的信息回答不知道」。编号是为了让模型在回答时能引用来源,方便溯源。
顺序上,把相似度最高的块放在最前面和最后面,中间放次相关的。这是对抗「中间迷失」的一个小技巧——模型对开头和结尾的内容注意力更集中。如果检索结果之间有明显冲突,别指望模型自己判断,应该在提示词里说明「如资料冲突,以编号小的为准」,把决策权收回来。
注意:RAG 不是银弹。如果检索阶段就没召回正确内容,后面提示词写得再漂亮也救不回来。排查问题时永远先看召回,再看生成。
5. 避坑与排查:那些让我加班到凌晨的坑
5.1 输出格式时好时坏,正则匹配频繁失败
现象:同样的提示词,大部分时候输出标准 JSON,偶尔多一句「好的」或者套个代码块,导致解析报错。
原因:模型在生成时受前文影响,如果历史对话里有非结构化内容,它会模仿那种风格。另外温度设得偏高也会增加格式漂移的概率。
解决:把结构化任务的温度压到 0.2 以下;在提示词末尾再强调一次「只输出 JSON」;代码里加三层解析兜底(见 3.3 节)。如果还不行,考虑用支持结构化输出的接口参数强制约束。
5.2 模型一本正经地编造不存在的信息
现象:问它某份文档里的具体条款,它答得头头是道,但原文里根本没这句话。
原因:大语言模型的本质是概率生成,不是数据库查询。当上下文里没有相关信息时,它会用训练时学到的「合理模式」补全,看起来特别可信。
解决:RAG 场景下强制要求「只依据资料回答」;给不出答案时允许它说「不知道」;关键业务加人工复核或二次验证。别指望靠提示词完全消除幻觉,只能压制到可接受范围。
5.3 上下文塞太长,响应慢且答非所问
现象:把整份文档塞进去后,首 Token 延迟从 1 秒涨到十几秒,回答还开始跑偏。
原因:输入 Token 数逼近窗口上限,注意力被稀释,模型抓不住重点。同时长输入直接推高推理成本。
解决:先检索再拼装,别整份塞;控制单次输入在窗口的 60% 以内;对超长文档做分层摘要,先摘要再检索。
5.4 微调后模型「变傻」了,通用能力下降
现象:用业务数据微调后,特定任务确实好了,但模型变得只会说那一类话,其他问题答得一塌糊涂。
原因:微调数据分布太窄,模型过拟合到特定模式,发生了灾难性遗忘。
解决:微调数据里混入 10% 到 20% 的通用指令数据;用 LoRA 而非全参数微调,降低对原权重的破坏;微调后跑一轮通用能力回归测试,别只看目标任务指标。
5.5 多轮对话里模型「忘记」了前面的约定
现象:第一轮说好「回答用中文」,第三轮它开始飙英文;前面定义的字段名,后面又变了。
原因:多轮对话的历史被截断,或者历史消息没有按正确角色拼接,模型看不到早期的约定。
解决:把关键约定写进系统提示词(system prompt),而不是放在第一轮用户消息里,系统提示词通常优先级更高且不易被截断;控制历史轮数,超长时做摘要压缩而不是硬截断。
6. 进阶技巧:用评测集把「感觉还行」变成「有数」
前面讲的都是怎么让模型跑起来,这一章讲怎么判断它跑得好不好。我早期最大的毛病就是靠肉眼抽查,改一版提示词,看几个例子觉得「好像好了」,上线后才发现是错觉。后来逼自己建了一套最小评测集,才把优化从玄学变成工程。
做法很简单:从真实业务里挑 50 到 100 条输入,人工标注好期望输出,存成一个 JSONL 文件。每次改提示词、换模型、调参数,都跑一遍这个集子,算准确率、格式合规率、平均延迟三个指标。别小看这个动作,它能在你自我感觉良好的时候泼一盆冷水。
import json def run_eval(prompt_fn, model_call, eval_path="eval_set.jsonl"): total, correct, format_ok = 0, 0, 0 for line in open(eval_path, encoding="utf-8"): case = json.loads(line) output = model_call(prompt_fn(case["input"])) total += 1 # 格式合规:能否解析成 JSON try: parsed = json.loads(output) format_ok += 1 except json.JSONDecodeError: continue # 内容正确:关键字段是否与标注一致 if all(parsed.get(k) == v for k, v in case["expected"].items()): correct += 1 print(f"样本数 {total},格式合规率 {format_ok/total:.2%},内容准确率 {correct/total:.2%}")这段代码故意写得朴素,因为它要能随时改。expected里只放最关键的字段,别追求全字段匹配,否则准确率会低到没有参考价值。指标出来之后,格式合规率低于 95% 就先修格式,内容准确率低于 80% 就回去看检索和提示词。
还有一个我后来才养成的习惯:给每次实验留记录。改了哪个参数、指标涨了还是跌了、为什么这么改,记在一个简单的表格里。不然一周之后你根本想不起来当初为什么把温度从 0.7 调到 0.3。这套评测加记录的习惯,比任何单个技巧都值钱——它让你从「调参靠感觉」变成「优化有依据」。希望帮到你。
本文还有配套的精品资源,点击获取