1. 环境准备与核心概念扫盲

大家好,我是老张,在AI大模型和智能硬件这块儿摸爬滚打了十几年。今天咱们不聊那些虚的,直接上手一个最近很多朋友在问的实战问题:如何用最新的 openai-1.60.2 这个库,去调用 DeepSeek 的深度思考模型(deepseek-reasoner),并且把模型那“神神叨叨”的推理过程给完整地“掏”出来。

你可能已经试过官方文档里给的例子,但发现 response.choices[0].message.reasoning_content 这个属性根本不存在,直接报错。别急,这坑我踩过,不是你的问题,而是 API 调用上有个非常关键但容易被忽略的“开关”没打开。咱们今天的目标,就是把这个开关找到,并且让你不仅能拿到最终答案,还能像看侦探破案一样,看到模型完整的思考链条。

首先,咱们得把“战场”准备好。核心就两个东西:Python 环境和 OpenAI 库。我知道很多新手朋友一看到“环境配置”就头疼,咱们这次把它简化到极致。你不需要搞什么复杂的虚拟环境,只要你电脑上有个能用的 Python(建议 3.8 以上版本),打开你的终端(Windows 叫命令提示符或 PowerShell,Mac/Linux 叫 Terminal),输入下面这行命令,一切就绪:

pip install openai==1.60.2

为什么非要指定 1.60.2 这个版本?这里有个小门道。OpenAI 的 Python SDK 更新很快,不同版本对 API 参数的支持、返回对象的属性可能会有细微差别。1.60.2 是目前(我写这篇文章时)一个非常稳定且功能完善的版本,它能很好地支持我们后面要用到的一个关键参数 extra_body。如果你用太老的版本,可能压根没这个参数;用太新的,又可能遇到别的兼容性问题。所以,锁定这个版本能避免很多不必要的麻烦,这是我实测下来的经验。

接下来,你需要一个 DeepSeek 的 API Key。这个就像你去银行取钱的密码,没有它,服务大门不会为你打开。获取路径很简单:去 DeepSeek 的官网,注册登录后,通常在“控制台”或“个人中心”里就能找到创建和管理 API Key 的地方。创建一个新的 Key,然后把它像宝贝一样存好,最好放在系统的环境变量里,别直接硬编码在代码里,以防不小心上传到公开的 GitHub 仓库导致密钥泄露。为了方便演示,我下面的代码会直接写出来,但你实际使用时一定要换成自己的。

好了,基础装备齐了。咱们再花一分钟理解下今天的主角:deepseek-reasoner,很多人也叫它 DeepSeek-R1。它和普通的聊天模型(比如 gpt-4)最大的不同,就是它被专门设计来进行“深度思考”。普通模型给你的是“答案”,而 deepseek-reasoner 除了答案,还能给你一份详细的“解题草稿”。这个草稿,就是 reasoning_content(推理内容)。想象一下,你问一个数学学霸“9.11 和 9.8 哪个大?”,普通模型可能直接告诉你“9.8 大”。但 deepseek-reasoner 会把它内心的嘀咕全说出来:“嗯,我先看个位,都是9,平手。再看十分位,一个是1,一个是8…等等,9.8是不是可以写成9.80?让我换算成分数比较一下…” 这个过程对于调试、教育、或者需要理解模型决策逻辑的场景来说,价值连城。

2. 从失败到成功:关键参数揭秘

现在,我们直接进入实战。很多朋友第一步就卡住了,因为照着一些基础示例写,根本拿不到推理内容。我们来重现一下这个“经典失败案例”:

from openai import OpenAI

client = OpenAI(
    api_key="你的-DeepSeek-API-Key-在这里",
    base_url="https://api.deepseek.com" # 注意:必须指向DeepSeek的端点
)

messages = [{"role": "user", "content": "9.11 和 9.8,哪个数字更大?"}]

response = client.chat.completions.create(
    model="deepseek-reasoner", # 指定使用深度思考模型
    messages=messages
)

# 尝试获取思考过程 —— 这里会报错!
reasoning_content = response.choices[0].message.reasoning_content
print(reasoning_content)

运行这段代码,你很可能会得到一个 AttributeError,提示 'ChatCompletionMessage' object has no attribute 'reasoning_content'。心凉了半截,对吧?我开始也这样。问题出在哪?问题在于,默认情况下,DeepSeek 的 API 为了节省带宽和加快响应,是不返回那个庞大的推理过程的。它只返回最终的 content(答案)。你需要显式地“告诉”API:“嘿,把思考的草稿纸也一起给我!”

这个“告诉”的方式,就是通过一个叫做 extra_body 的参数。这是 OpenAI SDK 提供的一个“后门”,允许你传递一些非标准但特定供应商支持的参数。对于 DeepSeek,这个魔法开关就是 {"return_reasoning": True}。让我们看看修正后的“成功代码”:

from openai import OpenAI

client = OpenAI(
    api_key="你的-DeepSeek-API-Key-在这里",
    base_url="https://api.deepseek.com"
)

messages = [{"role": "user", "content": "9.11 和 9.8,哪个数字更大?"}]

response = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    # 关键在此:通过 extra_body 传递供应商特定参数
    extra_body={
        "return_reasoning": True
    }
)

# 思考过程藏在这里!
reasoning = response.choices[0].message.model_extra['reasoning_content']
final_answer = response.choices[0].message.content

print("---------------------------思考过程---------------------------")
print(reasoning)
print("\n---------------------------最终答案---------------------------")
print(final_answer)

运行这段代码,你会看到控制台哗啦啦输出一大段文字,前面是模型一步步的自我对话和推理,最后才给出简洁的答案。这就对了!这里有两个至关重要的变化:

  1. 添加了 extra_body={"return_reasoning": True}:这是向 DeepSeek API 发出的明确指令,要求返回推理内容。
  2. 从 model_extra 中获取 reasoning_content:推理内容不再是一个直接属性,而是被放在了 message 对象的 model_extra 字典里。这是一个非常关键的细节,也是很多教程没讲清楚的地方。

你可以把 model_extra 理解成一个“杂物间”。不同模型供应商(如 OpenAI, DeepSeek, Anthropic)可能会有自己独特的返回字段,SDK 为了保持统一的接口,就把这些非标准的、供应商特定的数据都塞进了这个“杂物间”。你需要用钥匙(['reasoning_content'])去里面把想要的东西拿出来。

3. 参数进阶:控制推理的深度与长度

成功拿到推理过程只是第一步。在实际项目中,你往往需要对模型的“思考”进行更精细的控制。比如,你觉得它想得太啰嗦了,或者你只关心它最关键的一步推理。DeepSeek-R1 的 API 提供了一些参数来满足这些需求,但这些参数同样需要通过 extra_body 来传递。我把它总结成一个“调参工具箱”,你可以根据场景选用。

最常用的一个参数是 reasoning_format。它决定了推理内容的呈现格式。默认是 "natural",也就是我们上面看到的那种自然语言、自问自答的散文式思考。但它还支持 "concise"(简洁)模式。我们来对比一下:

response_natural = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    extra_body={
        "return_reasoning": True,
        "reasoning_format": "natural"  # 默认值,可省略
    }
)
reasoning_natural = response_natural.choices[0].message.model_extra['reasoning_content']
print("自然格式(前500字符):", reasoning_natural[:500])

response_concise = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    extra_body={
        "return_reasoning": True,
        "reasoning_format": "concise"  # 切换到简洁格式
    }
)
reasoning_concise = response_concise.choices[0].message.model_extra['reasoning_content']
print("\n简洁格式:", reasoning_concise)

你会发现,"concise" 格式的输出更像是一个要点列表或逻辑步骤的摘要,去掉了许多“嗯…让我想想”、“那么…”这样的口语化连接词,直接呈现核心推理步骤。这对于需要后续程序化处理推理内容的场景(比如提取关键决策点)非常有用。

另一个你可能关心的是 推理长度。复杂的任务可能导致推理过程非常长。虽然目前 DeepSeek API 没有直接提供“最大推理令牌数”这样的参数来硬性截断推理过程,但你可以通过一个间接但有效的方式来施加影响:调整 max_completion_tokens。这个参数控制的是最终 content(答案)的最大长度,但模型在生成最终答案前,会先进行内部推理。如果给最终答案的“预算”很少,模型可能会被迫进行更简洁的思考。不过要注意,这只是一个软约束,并非精准控制。

response = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    max_completion_tokens=100,  # 限制最终答案长度
    extra_body={
        "return_reasoning": True
    }
)

此外,所有标准的聊天补全参数,如 temperature(创造性)、top_p(核采样)等,都对 deepseek-reasoner 有效。temperature 低一些(比如0.2),会让推理过程更确定、更聚焦;调高一些,则可能看到更多探索性的、发散的思考路径。你可以像下面这样组合使用:

response = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    temperature=0.3,
    top_p=0.9,
    extra_body={
        "return_reasoning": True,
        "reasoning_format": "natural"
    }
)

为了让你更一目了然,我把这些关键参数和它们的影响做成了下面这个表格,方便你快速查阅和组合:

参数位置参数名可选值/类型作用对推理过程的影响
extra_body 内return_reasoningTrue/False总开关,是否返回推理内容。不设为 True 则根本拿不到推理。
extra_body 内reasoning_format"natural"(默认)
"concise"
控制推理内容的文本格式。natural 更口语化、详细;concise 更结构化、精炼。
主参数max_completion_tokens整数限制最终答案的最大令牌数。间接影响,可能促使模型进行更简练的推理。
主参数temperature0.0 ~ 2.0控制输出的随机性。值低则推理路径更确定、保守;值高则可能产生更多探索性思考。
主参数top_p0.0 ~ 1.0核采样,影响令牌选择的多样性。与 temperature 配合,影响推理时的候选思路选择。

4. 实战场景与代码封装

了解了基本调用和参数,咱们来点更实际的。你肯定不会每次都写一遍 extra_body 和从 model_extra 里取数据这么原始的代码。最好的办法是把它封装成一个好用、健壮的函数。下面这个 get_reasoning_response 函数是我在几个项目中用下来的,加入了错误处理和类型提示,你可以直接抄走。

from typing import Optional, Dict, Any
from openai import OpenAI, OpenAIError

def get_reasoning_response(
    client: OpenAI,
    user_prompt: str,
    system_prompt: Optional[str] = None,
    reasoning_format: str = "natural",
    **kwargs
) -> Dict[str, Any]:
    """
    获取 DeepSeek-R1 模型的回复,包含推理过程和最终答案。

    参数:
        client: 已初始化的 OpenAI 客户端实例。
        user_prompt: 用户的提问。
        system_prompt: 可选的系统指令,用于设定模型角色。
        reasoning_format: 推理格式,'natural' 或 'concise'。
        **kwargs: 其他传递给 `chat.completions.create` 的参数(如 temperature, max_tokens)。

    返回:
        一个字典,包含:
        - 'success': 布尔值,表示调用是否成功。
        - 'reasoning': 推理内容字符串(如果成功)。
        - 'answer': 最终答案字符串(如果成功)。
        - 'error': 错误信息字符串(如果失败)。
    """
    # 构建消息列表
    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": user_prompt})

    try:
        response = client.chat.completions.create(
            model="deepseek-reasoner",
            messages=messages,
            extra_body={
                "return_reasoning": True,
                "reasoning_format": reasoning_format
            },
            **kwargs  # 传递其他参数如 temperature, max_completion_tokens
        )

        # 安全地提取内容
        message = response.choices[0].message
        reasoning = message.model_extra.get('reasoning_content', '') if message.model_extra else ''
        answer = message.content or ''

        return {
            "success": True,
            "reasoning": reasoning,
            "answer": answer,
            "full_response": response  # 保留完整响应对象以备不时之需
        }

    except OpenAIError as e:
        # 处理API错误,如网络问题、鉴权失败、额度不足等
        return {
            "success": False,
            "reasoning": "",
            "answer": "",
            "error": f"OpenAI API Error: {e}"
        }
    except KeyError as e:
        # 处理响应结构不符合预期的情况
        return {
            "success": False,
            "reasoning": "",
            "answer": "",
            "error": f"Unexpected response structure: {e}"
        }
    except Exception as e:
        # 捕获其他所有意外异常
        return {
            "success": False,
            "reasoning": "",
            "answer": "",
            "error": f"Unexpected error: {e}"
        }

# 使用示例
client = OpenAI(api_key="你的-DeepSeek-API-Key-在这里", base_url="https://api.deepseek.com")

result = get_reasoning_response(
    client=client,
    system_prompt="你是一个严谨的数学老师,请逐步推理并展示你的思考过程。",
    user_prompt="一个半径为5厘米的圆的面积是多少?",
    reasoning_format="concise",
    temperature=0.2
)

if result["success"]:
    print("=== 推理过程 ===")
    print(result["reasoning"])
    print("\n=== 最终答案 ===")
    print(result["answer"])
else:
    print(f"调用失败: {result['error']}")

这个函数的好处显而易见:一是安全,用 try-except 把可能出现的网络错误、API错误、数据结构错误都兜住了,不会让你的程序因为一次调用失败就崩溃;二是方便,你只需要关心你的问题(user_prompt)和想要的格式(reasoning_format),其他细节它帮你处理;三是灵活,通过 **kwargs 你可以传入任何 chat.completions.create 支持的标准参数。

接下来,我们看看这个功能在真实场景里能怎么玩。场景一:智能辅导系统。你可以把学生的数学题丢给模型,不仅得到对错,还能拿到完整的解题思路。这个思路可以展示给学生看,让他明白自己卡在哪一步。甚至,你可以写个简单的程序,去解析推理内容中的关键步骤(比如“根据勾股定理”、“化简得到”这类关键词),实现更智能的交互。

场景二:复杂决策日志。假设你用它来分析“是否应该投资某个项目?”。模型在给出“建议投资”或“不建议”的同时,其 reasoning_content 里会包含它对市场风险、团队能力、财务数据等各个维度的权衡过程。这份“思考日志”对于审计、解释AI决策至关重要,远比一个干巴巴的结论有价值。

场景三:创意写作与头脑风暴。你可以让模型写一首关于“秋天”的诗,并要求返回推理。你会看到它如何从“秋天的意象(落叶、丰收)”联想到“情感的基调(忧伤、喜悦)”,再尝试组织韵律和比喻。这个过程本身,就是学习创意生成机制的绝佳材料。

5. 避坑指南与性能优化

技术路上坑难免,我把自己和朋友们遇到过的问题总结一下,希望能帮你省下几个小时甚至几天的调试时间。

第一个大坑:版本兼容性。 我开头就强调了用 openai==1.60.2,这不是开玩笑。有朋友用 0.28 之类的老版本,发现 extra_body 参数根本不起作用。也有用最新 1.70+ 版本的朋友,发现返回的对象结构又有点细微变化。在AI工具链快速迭代的今天,锁定一个经过验证的稳定版本是保证复现性的第一步。如果你因为项目依赖必须用其他版本,一定要先去 OpenAI 的官方 GitHub 仓库查一下该版本 ChatCompletion 的接口文档。

第二个坑:base_url 没改。 这是最经典的错误之一。OpenAI 客户端的默认 base_url 是指向 https://api.openai.com 的。如果你忘了改,就会用你的 DeepSeek Key 去调用 OpenAI 的接口,结果肯定是鉴权失败。所以,初始化客户端时,务必记得写上 base_url="https://api.deepseek.com"。

第三个坑:网络超时与重试。 推理过程很长,所以 API 调用的耗时通常比普通聊天补全要久。默认的请求超时时间可能不够。我建议在初始化客户端时,或者单次请求中,显式设置一个更长的超时时间。

from openai import OpenAI

# 方法1:初始化客户端时设置默认超时
client = OpenAI(
    api_key="你的-DeepSeek-API-Key-在这里",
    base_url="https://api.deepseek.com",
    timeout=30.0,  # 将默认超时设为30秒
    max_retries=2   # 增加重试次数
)

# 方法2:单次请求覆盖超时(openai SDK 某些版本支持)
response = client.chat.completions.create(
    model="deepseek-reasoner",
    messages=messages,
    extra_body={"return_reasoning": True},
    timeout=60.0  # 这次请求我愿意等60秒
)

第四个坑:令牌消耗与成本。 这一点非常重要!deepseek-reasoner 的计费是同时计算输入令牌、推理过程令牌和输出令牌的。也就是说,那份长长的 reasoning_content 也是要花钱的。如果你的应用场景不需要推理过程,只是想要一个“思考更深入”的答案,那么调用 deepseek-reasoner 但不要求返回推理(即不传 return_reasoning=True),在成本上可能是不划算的,因为它的单价可能比普通聊天模型高。务必在 DeepSeek 官方定价页面确认当前费率。对于需要频繁调用的生产环境,一定要在代码里估算令牌数,做好预算控制。你可以通过 response.usage 对象查看具体的消耗明细。

关于性能优化, 我有两个实用建议。第一是异步调用。如果你的应用需要同时处理多个用户查询,使用异步客户端可以极大提升吞吐量,避免因为等待一个长思考的响应而阻塞整个程序。

import asyncio
from openai import AsyncOpenAI

async def async_get_reasoning():
    aclient = AsyncOpenAI(api_key="你的-DeepSeek-API-Key-在这里", base_url="https://api.deepseek.com")
    response = await aclient.chat.completions.create(
        model="deepseek-reasoner",
        messages=[{"role": "user", "content": "问题"}],
        extra_body={"return_reasoning": True}
    )
    # ... 处理响应

# 在异步框架中运行

第二是流式输出。对于推理过程特别长的任务,等待完整响应可能需要几十秒。使用流式响应(Streaming)可以让推理内容像打字一样一点点显示出来,用户体验会好很多。不过,流式响应处理起来稍微复杂一点,你需要监听事件流,并自己拼接 reasoning_content 的片段。DeepSeek API 是支持流式传输的,你可以尝试在 create 方法中加入 stream=True 参数,然后迭代响应。

更多推荐