FEATURED · 精选文章

DeepSeek API上下文感知调用实战:多轮对话与记忆管理

发布时间 / 2026/9/18 12:59:05
来源 / 创域科博编辑部
栏目 / 资讯中心
DeepSeek API上下文感知调用实战:多轮对话与记忆管理 简介面向自然语言处理开发者、算法工程师与技术管理者的DeepSeek技术解析文档系统拆解上下文感知接口的完整实现路径。文档共23页先介绍语义理解的重要性和DeepSeek的诞生背景再深入讲解Transformer基础、词嵌入与位置编码、多头注意力机制、上下文建模、语义相似度计算等核心技术同时剖析输入预处理、上下文建模、语义理解、输出生成等关键模块并给出性能优化策略与实际项目中的应用案例。最后还将DeepSeek与传统方法、其他深度学习模型对比梳理可解释性、数据质量、计算资源等挑战及未来多模态发展趋势。资源为1个PDF文件大小1.63MB压缩包内文件类型单一目录结构清晰、文字图表完整可离线阅读。当前已有100人学习下载适合需要做技术选型、实践落地或系统学习语义理解技术的读者。 先给结论把 DeepSeek 当 AI 接口用时上下文感知不是一个布尔开关而是一套请求结构。接口层真正在做的是把用户这句话、前面八轮对话、系统设置好的业务边界和外部检索结果打包成一个 messages 数组。DeepSeek 的聊天补全端点之所以能成为“语义理解新标杆”不是因为它把历史记忆藏在某个隐状态里而是因为它把上下文显式暴露给了调用方。也就是说谁拼上下文、怎么拼、拼多长决定了下游语义理解的上限。这篇拆解要解决的问题很具体如何在 DeepSeek API 之上写出可落地的上下文感知调用层让模型在多轮对话、长文档和工具调用场景里不再“失忆”。2. 从零调用 DeepSeek API最小上下文感知请求2.1 DeepSeek API 的 OpenAI 兼容形态与鉴权DeepSeek 开放平台提供的是 OpenAI 兼容的 REST 接口这意味着先前用过 OpenAI SDK 的工程团队几乎不需要改业务代码。我一般会在环境变量里维护DEEPSEEK_API_KEY和DEEPSEEK_BASE_URL而不是把密钥写死在代码里。这里的上下文感知第一步就是把调用地址、模型名和身份信息固定下来让上层业务只需要关心 messages 怎么拼。最小请求长这样先把单轮语义理解跑通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: 帮我把这句话分类明天下午三点的会议改到周五。} ] }这里model参数指向deepseek-chat这是官方对话模型在不少接入示例里使用的模型名messages是接口唯一真正关心的上下文容器。你不需要额外传 session_id也不需要传历史文本拼接字符串只要按顺序把system、user、assistant消息放进数组模型就会把它们当作连续发生的对话。system消息在这里相当于语义理解的“规则层”它的内容会直接影响后面所有 user 消息的解读方式。2.2 用 requests 组装多轮上下文的最小示例curl 适合验证连通性回到工程代码里我更推荐直接用requests写一个薄封装。多轮上下文最容易出错的地方不是接口参数而是把 history 拼成带角色标签的字典数组时漏掉某条 assistant 回复。下面这段代码用一个conversation_history列表承载完整上下文每次请求前追加当前用户输入请求后把模型回复也追加进去保证角色交替完整import requests DEEPSEEK_API_KEY sk-... DEEPSEEK_URL https://api.deepseek.com/chat/completions def chat_with_context(history, user_input, modeldeepseek-chat): history.append({role: user, content: user_input}) resp requests.post( DEEPSEEK_URL, headers{ Authorization: fBearer {DEEPSEEK_API_KEY}, Content-Type: application/json, }, json{ model: model, messages: history, temperature: 0.2, }, timeout30, ) resp.raise_for_status() data resp.json() reply data[choices][0][message][content] history.append({role: assistant, content: reply}) return reply history [ {role: system, content: 你是语义理解引擎负责抽取用户消息里的时间和地点。} ] print(chat_with_context(history, 明天下午三点的会议改到周五)) print(chat_with_context(history, 改到几点))代码里的history.append不是可选项而是上下文感知接口的核心动作模型没有跨请求记忆第二次提问的“改到几点”之所以还能理解“改”的对象是会议是因为第一次对话已经被完整带进了请求。temperature我调到了 0.2语义抽取类任务更看重确定性温度越低输出越接近单一稳定解读。2.3 用 openai SDK 调用 deepseek-chat 与超时参数如果你的项目里已经装了openaiPython SDK可以省掉手动写requests的工作。只需要用OpenAI客户端指定base_url为 DeepSeek 兼容端点后续的client.chat.completions.create调用方式和你熟悉的完全一样from openai import OpenAI client OpenAI( api_keysk-..., base_urlhttps://api.deepseek.com, timeout30.0, ) def chat_once(messages): resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperature0.2, max_tokens1024, streamFalse, ) return resp.choices[0].message.content messages [ {role: system, content: 你是语义理解接口只输出 JSON。}, {role: user, content: 从这句话中提取出发城市和到达城市周五早上从上海去杭州。}, ] print(chat_once(messages))max_tokens1024是给语义理解任务设的合理上限因为抽取结果通常很短留太大反而会让接口在异常输入时多输出解释性废话。timeout参数比多数人以为的重要DeepSeek 接口在负载高时可能出现“服务器繁忙请稍后再试”之类的错误客户端不设超时会导致业务线程被拖死。建议 30 秒起步配合指数退避重试而不是单纯调大超时。2.4 第一次请求失败的排查点第一次调不通90% 的问题出在下面这四个地方。看一眼响应状态码基本就能定位状态码常见原因排查方向401API Key 无效或缺失检查Authorization头确认环境变量没有被 shell 吞掉429触发速率限制降低 QPS或确认账户余额是否充足400messages 格式错误检查角色必须是 system/user/assistantcontent 不能为 None500服务端异常等待后重试过滤掉最近一条消息再发另外一个经常踩的坑是 base_url 写错。不同的 SDK 版本对 base_url 的拼接规则不完全一致有的会在你给的地址后面直接拼/chat/completions有的会拼/v1/chat/completions。我一般先看异常堆栈里的完整 URL再回推应该用https://api.deepseek.com还是https://api.deepseek.com/v1标准是以官方文档给出的那块地址为准。3. 上下文长度与记忆管理让接口记住该记住的3.1 DeepSeek 上下文窗口与 token 估算上下文感知接口并不是无限记忆。每次请求发出去的 messages 会全部计入上下文窗口超出一段距离后最前面的消息会被模型忽略表现为“聊着聊着忘了最初的需求”。所以做上下文感知的第二件事是学会给对话记账。DeepSeek 的 tokenizer 我会用 OpenAI 的cl100k_base做一个近似估算虽然不完全等价但足以判断截断策略是否合理import tiktoken encoding tiktoken.get_encoding(cl100k_base) def count_tokens(messages): total 0 for msg in messages: total 4 len(encoding.encode(msg[content])) total len(encoding.encode(msg[role])) return total 2 # 大致覆盖消息协议开销 messages [ {role: system, content: 你是客服语义理解引擎。}, {role: user, content: 我的订单为什么还没有发货}, ] print(count_tokens(messages))count_tokens里额外加的 4 和 2 是消息结构本身的开销目的是留出余量避免实际请求时因为 tokenizer 差异而超窗。拿到这个数字后你应该把它和模型允许的最大上下文做减法而不是和输入字符数做减法。上下文窗口是在接口文档里查到的不要靠猜不同时间的模型版本给出的窗口大小会不一样。3.2 滑动窗口截断多轮对话最直接的历史管理策略是滑窗给整个对话设置一个 token 预算超过预算就丢掉最早的非 system 消息。实现时要注意保留首条 user 消息因为用户最初诉求往往是整个对话的锚点MAX_CONTEXT_TOKENS 6000 def trim_to_fit(messages): system_msgs [m for m in messages if m[role] system] history [m for m in messages if m[role] ! system] while history and count_tokens(system_msgs history) MAX_CONTEXT_TOKENS: # 永远保留第一条 user 消息其余按时间从旧到新丢弃 for i, m in enumerate(history): if i 0 and m[role] user: history.pop(i) break else: history.pop(0) return system_msgs history上面这段代码的逻辑是先固定 system 消息不动然后循环检查总 token 数。一旦超预算就尝试从历史里找到第一条非锚点的 user 消息删除。找不到可删的 user 时才退化成从最前面弹出。这个实现比直接history history[-10:]更合理因为它不会把最初的意图丢干净也不会因为某条超长消息而误删整段历史。3.3 用摘要压缩历史滑窗的缺点是粗暴删掉的消息可能包含关键信息。更聪明的做法是用模型自己压缩把较旧的一段对话交给 DeepSeek让它提炼成一段摘要再把摘要作为一条system或user消息放回上下文。压缩后的语义信息密度远高于原文适合长时间会话。def summarize_old_messages(old_messages): summary_prompt ( 请把下面的对话压缩成一段200字以内的摘要保留已经确认的事实、用户诉求和未解决问题\n\n \n.join(f{m[role]}: {m[content]} for m in old_messages) ) return chat_once([ {role: system, content: 你是对话摘要器只输出摘要正文。}, {role: user, content: summary_prompt}, ])摘要压缩不能每次请求都做那样成本会很高。常见做法是维护一个“摘要阈值”当历史 token 数超过预算的一半时触发一次摘要然后用摘要替换掉最旧的一半历史。需要留意的是摘要本身也占 token所以摘要生成后要再跑一次trim_to_fit确认总长。压缩后的摘要最好标记角色为system因为它的权威性高于普通历史对话模型会优先遵循它。3.4 长文档语义问答的分块与召回对话上下文之外另一个常见语义理解场景是长文档问答。直接把 PDF 或文档全文塞进 messages几乎必然超窗还会让模型被低频细节干扰。标准解法是先把文档切成 500 字左右的 chunk再根据当前用户问题做召回只把最相关的几段放进上下文。import numpy as np def recall_chunks(question_embedding, chunk_embeddings, top_k4): scores [] for idx, emb in enumerate(chunk_embeddings): cos_sim np.dot(question_embedding, emb) / ( np.linalg.norm(question_embedding) * np.linalg.norm(emb) ) scores.append((idx, cos_sim)) scores.sort(keylambda x: x[1], reversedTrue) return [idx for idx, _ in scores[:top_k]]这里的question_embedding和chunk_embeddings可以由任何 embedding 模型生成不一定要来自 DeepSeek。召回之后把命中的 chunk 按原始顺序拼接插到 system 提示词后面。这样上下文感知就从“记住全部”变成了“记住最相关的部分”这也是文档问答在真实业务里能够落地的关键接口侧不需要理解整本书只需要理解检索出来的那几页。4. 用上下文感知接口做业务语义理解提示词与结构化输出4.1 System Prompt把业务约束写进上下文同样是“帮我查一下”在客服系统里意味着查订单在库存系统里意味着查 SKU。决定模型往哪个方向理解的不是用户这句话本身而是 system prompt 里写明的业务边界。所以上下文感知接口的第四步是把你对业务规则的理解转成模型能执行的约束。下面这段 prompt 模板可以当成基线它明确告诉模型“你是语义理解引擎”限定输出格式并定义无法理解时的兜底行为。ORDER_SYSTEM 你是订单语义理解引擎。你会收到用户关于订单的提问请完成以下任务 1. 判断用户意图查询订单、修改订单、取消订单、其他 2. 抽取订单号、商品名、期望操作时间等关键槽位 3. 只输出 JSON不要输出解释文字。 JSON 结构 {intent: ..., slots: {...}, need_confirm: false} 如果信息不完整把 need_confirm 设为 true并在 slots 中标记缺失字段。 need_confirm这个字段是给上层业务用的不是给模型用的。它的作用是让模型在信息不全时主动说“我缺信息”而不是硬猜一个结论。把“允许不知道”写进 system prompt会显著降低语义理解接口在边缘输入上的错误率。注意 system prompt 不要写成对话腔模型对命令式说明的遵循度更高。4.2 Few-shot 示例与 JSON 输出System prompt 能说清规则但模型对抽象规则的理解没有对具体例子那么稳。少样本学习的意思是在 messages 里直接给出一两条“用户问题 - 标准输出”的示例让模型照着仿写。这在语义理解接口里几乎是性价比最高的调优手段DEMO_MESSAGES [ {role: system, content: ORDER_SYSTEM}, { role: user, content: 我的订单10086什么时候能送到 }, { role: assistant, content: {intent: 查询订单, slots: {order_id: 10086}, need_confirm: false} }, { role: user, content: 我要退货但忘了订单号。 }, { role: assistant, content: {intent: 取消订单, slots: {order_id: null}, need_confirm: true} }, { role: user, content: 能不能把发货地址改一下 }, ]示例要覆盖两种类型正常完整输入以及信息缺失的输入。这样模型才会在真实请求里复制出“信息不足时置 need_confirmtrue”的行为。少样本示例会增加上下文 token所以不要放太多每个意图两条就够如果示例超过十个模型反而会去模仿示例里的语气而不是遵守规则。4.3 Function Calling 把语义动作绑定到接口上下文感知不只是理解还要能触发动作。OpenAI 兼容接口普遍支持 tools 参数DeepSeek API 在不少接入场景里也走这套协议。做法是把可执行的操作声明成函数模型在理解用户意图后不是直接输出文字而是返回一个待调用的函数名和参数。tools [ { type: function, function: { name: query_order_status, description: 查询订单当前状态, parameters: { type: object, properties: { order_id: {type: string, description: 订单号} }, required: [order_id], }, }, } ] resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolstools, tool_choiceauto, )拿到resp.choices[0].message.tool_calls之后真正的动作由你的业务代码执行比如查询订单库。执行完的结果要再作为一条role: tool消息追加进 messages重新调用一次接口模型才会把结果组织成用户看得懂的回复。这个来回就是工具调用的“感知循环”上下文感知接口第一次从一个文本生成器变成了能操作业务系统的执行器。4.4 意图识别的最小实现如果不想引入复杂的 Agent 框架只做语义理解可以把 function calling 简化成意图分类。用 DeepSeek 做意图识别的最小实现只需要一个精心构造的 prompt 和一段格式校验代码import json def parse_json_response(content): content content.strip() if content.startswith(): content content.strip() if content.startswith(json): content content[4:] return json.loads(content) raw chat_once([ {role: system, content: 把用户问题分类为查天气/定闹钟/播放音乐/其他。只输出JSON。}, {role: user, content: 明天出门要不要带伞} ]) print(parse_json_response(raw))这段代码的关键不在 JSON 解析而在于把语义理解压缩成少数几个类别。类别越少模型越稳定类别之间若存在语义重叠需要先合并。实测中把“播放音乐”和“停止音乐”合并成“控制音乐”比强行细分更准确。意图识别做稳之后再逐步扩展实体抽取和槽位填充比一开始就上完整对话系统要务实很多。5. 进阶落地开发链路与实测验证5.1 在 VSCode 和 Codex 类工具里接入 DeepSeek语义理解接口不只在服务器上跑开发阶段也能直接接入编辑器。主流的做法是把 DeepSeek 配置成 OpenAI 兼容后端让 Continue、Codex 这类工具通过环境变量指向它。常见的配置片段类似于{ provider: openai, apiBase: https://api.deepseek.com/v1, apiKey: $DEEPSEEK_API_KEY, model: deepseek-chat }配置完成后在编辑器里选中一段代码让 DeepSeek 基于当前文件和选中内容作答。这里的上下文感知发生在 IDE 层插件会把打开的代码片段、选中内容和你的提问一起拼进 messages模型才能理解你在改哪一个函数、要解决什么编译错误。接入后建议先跑一个最小对齐测试让模型解释选中函数的作用确认 base_url 和鉴权没问题再开始日常使用。5.2 本地部署 DeepSeek 后暴露上下文感知接口有数据合规要求时可以在内网做本地部署。常见的部署思路是用推理框架拉起一个 OpenAI 兼容服务把请求地址指向本机端口上层业务代码不用改。vllm serve deepseek-ai/DeepSeek-V3 --served-model-name deepseek-chat --port 8000 curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model: deepseek-chat, messages: [{role: user, content: 测试}]}本地部署后上下文长度和并发量都变成你掌控的资源不再受上面代码里的预算默认值限制。唯一要提醒的是本地部署的语义理解效果与官方接口版本之间总存在差异上线前必须用同一套测试集在两端各跑一遍对比 F1 或准确率不能默认“开源权重等于线上服务”。5.3 三组必调的验证指标上下文感知接口的验收不能只看单个回答对不对要看多轮场景下的连续性。我会固定准备包含 20 个多轮问题的测试集重点看三组指标多轮指代消解率也就是第二次问“那个订单”时是否能找到正确目标槽位填充准确率关键槽位值是否正确落到 JSON以及结构化输出通过率响应是否能被json.loads直接解析。评测时可以给每组指标单独设通过线例如多轮指代消解率低于 80% 时优先检查历史截断是不是把关键信息丢掉了结构化输出通过率低于 95% 时在 system prompt 里追加一句“不要输出任何解释文字”。这三个指标调完上下文感知接口才算真正达到可以交给业务方使用的状态。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻