DeepSeek官方文档阅读笔记

DeepSeek今天发布了V3.2,深度思考模式首次支持工具调用,并且将最大输出token长度扩充至32k-64k。同时,发布了一个测试版的v3.2-speciale,该模型可以看做一个超级加强版长思考DeepSeek,具有出色的数学推理能力和高复杂任务解决能力。但既不支持工具调用,也没有针对日常应用场景进行专项优化,仅仅供探索模型能力边界研究使用。(消耗token量也显著更多)

image

 关注完了前沿,那么,如何在api调用中开启深度思考?

一. 开启深度思考

1. 设置model为'deepseek-reasoner'

2. model仍然设置成'deepseek-chat',同时设置thinking参数:

'thinking': {'type': 'enabled'}

如果使用openai sdk,需要这样传:

response = client.chat.completions.create(
    model='deepseek-chat',
    # ...
     extra_body={'thinking': {'type': 'enabled'}}
)

当开启深度思考后,返回字段中会比普通模型调用多一个reasoning_content参数(即深度思考的思维链内容),与content同级,在多轮对话时,官方推荐下一轮对话的输入仅包含上一轮的普通输出content,不会包含reasoning_content,来节省带宽。

二. 工具调用

目前DeepSeek仅支持function类型的工具调用。(有模型支持其它方式吗?待学习...)

首先我们要明白一个概念,大模型本身是没有函数执行能力的。所有的工具调用概念,指的是开发者预先定义好可以使用的函数,同时在请求时将可以调用的函数信息以标准json的形式编码,形成函数列表,用特定的tools参数传递给大模型,大模型经过思考,不输出文本,而是输出标准的json,内容为提示调用的函数名,和json编码过的函数参数。开发者拿到大模型输出后,解析输出对大模型要调用的方法+参数手动执行,并将结果跟随消息记录一同返回给大模型(以ToolMessage的形式),大模型再进行下一步思考。

重点是:函数是开发者自己定义的,执行也是开发者执行的。(当然现在很多agent框架替我们封装好了这些,比如langchain,google ADK)

image

 整个过程示意图,可以看出在最终答复用户之前,实际上调用了2次大模型(根据思考情况可以重复调用更多次)

image

 多次的示意图。

示例代码:

这段代码定义了两个工具:get_today_date和get_weather,然后定义了run_turn方法进行思考-行动的循环,直到得到答案。

在把工具调用结果返回给大模型继续思考时,要注意将tool_call_id带上,帮助模型确认结果。

在两个问题之间,会清空reasoning_content,只带上正式回答的历史记录。

这个例子是deepseek官方的例子,我捋清思路自己写了一遍,建议读者朋友也自己实现一次,加深记忆。

  1 from openai import OpenAI
  2 import json
  3 import os
  4 from dotenv import load_dotenv
  5 
  6 load_dotenv()
  7 
  8 # 第一步,创建一个ds client
  9 client = OpenAI(
 10     api_key=os.getenv('DEEPSEEK_API_KEY'),
 11     base_url=os.getenv('DEEPSEEK_API_BASE')
 12 )
 13 
 14 # 第二步,定义tools
 15 
 16 def get_today_date() -> str:
 17     """获取今天的日期,返回‘YYYY-MM-DD’格式"""
 18     return '2025-12-02'
 19 
 20 def get_weather(date: str, location: str) -> str:
 21     """根据日期和地区信息获取天气"""
 22     return 'Windy -8~3°C'
 23 
 24 tools = [
 25     {
 26         'type': 'function',
 27         'function': {
 28             'name': 'get_today_date',
 29             'description': '获取今天的日期',
 30             'parameters': {
 31                 'type': 'object',
 32                 'properties': {}
 33             }
 34         }
 35     },
 36     {
 37         'type': 'function',
 38         'function': {
 39             'name': 'get_weather',
 40             'description': '获取某个地区的天气,用户需要输入地区名和日期',
 41             'parameters': {
 42                 'type': 'object',
 43                 'properties': {
 44                     'date': {
 45                         'type': 'string',
 46                         'description': '要获取天气的日期,"YYYY-MM-DD"格式',
 47                     },
 48                     'location': {
 49                         'type': 'string',
 50                         'description': '城市名称'
 51                     }
 52                 },
 53                 'required': ['date', 'location']
 54             }
 55         }
 56     }
 57 ]
 58 
 59 TOOL_MAP = {
 60     'get_today_date': get_today_date,
 61     'get_weather': get_weather
 62 }
 63 
 64 # 第三步,定义api请求
 65 
 66 def send_message(messages: list):
 67     """
 68     原生ds API请求方法,返回一条消息(可能是工具调用,也可能是文本)
 69     """
 70     response = client.chat.completions.create(
 71         model='deepseek-chat',
 72         messages=messages,
 73         tools=tools,
 74         extra_body={'thinking': {'type': 'enabled'}}
 75     )
 76     return response.choices[0].message
 77 
 78 # 第四步 一次问题的思考、调用工具直到回答过程
 79 def run_turn(messages: list = []) -> str:
 80     """
 81     获取一个比较复杂问题的回答,模型经过思考、调用工具、处理工具返回,最终返回文本时,得到答案
 82     Args:
 83         query: str 当前问题
 84         historys: messages[] 历史消息列表,可以为空
 85     Returns:
 86         问题的答案,一个字符串
 87     """
 88     
 89     # 循环进行思考和行动
 90     max_iterations = 4
 91     step = 0
 92     while step < max_iterations:
 93         # 请求得到答案并进行处理
 94         res_message = send_message(messages)
 95         print('======== 获取到一轮结果 ========')
 96         print(res_message)
 97         # 先把得到的消息存入列表
 98         messages.append(res_message)
 99         # 分析返回消息类型,是工具调用还是输出文本
100         tool_calls = res_message.tool_calls
101         if not tool_calls or len(tool_calls) == 0:
102             # 没有调用工具,判断为可以返回答案了
103             break
104         # 调用工具
105         # 每次只调用一个工具
106         try:
107             func_name = tool_calls[0].function.name
108             func_id = tool_calls[0].id
109             func_args = json.loads(tool_calls[0].function.arguments)
110             func_res = TOOL_MAP[func_name](**func_args)
111         except Exception as e:
112             print(f'调用工具{func_name}过程出错:{e}, 参数为:{func_args}')
113         # 组装结果为ToolMessage
114         messages.append({
115             'role': 'tool',
116             'tool_call_id': func_id,
117             'content': func_res
118         })
119         step += 1
120     
121     return messages
122 
123 # 为了节省请求token,在每次问题结束时清空上次的reasoning_content
124 def clear_reasoning_content(messages: list) -> list:
125     for msg in messages:
126         if hasattr(msg, 'reasoning_content'):
127             msg.reasoning_content = None
128     return messages
129 
130 def main():
131     messages = [
132         {
133             'role': 'system',
134             'content': '你是一个会使用工具帮助用户回答问题的助手,每一步只能调用一个工具,直到获得足够信息来回答问题'
135         },
136         {
137             'role': 'user',
138             'content': '北京明天的天气如何?'
139         }
140     ]
141     res_msgs = run_turn(messages)
142     print('========= 第一个问题的答案是: =========')
143     print(res_msgs[-1].content)
144     # 再问一个相同问题,测试读取历史记录功能
145     messages = clear_reasoning_content(res_msgs)
146     messages.append({
147         'role': 'user',
148         'content': '北京12月3日的天气如何?'
149     })
150     res_msgs = run_turn(messages)
151     print('========= 第二个问题的答案是: =========')
152     print(res_msgs[-1].content)
153     
154 
155 if __name__ == '__main__':
156     main()
157 
158     
View Code

返回:

======== 获取到一轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_9DQOv3s183R3bYTlLtaP686H', function=Function(arguments='{}', name='get_today_date'), type='function', index=0)], reasoning_content='用户想知道北京明天的天气。我需要先获取今天的日期,然后确定明天的日期,再查询北京明天的天气。让我先调用获取今天日期的工具。')
======== 获取到一轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_3hxZEjfchuaCb3D0GkrUJkxm', function=Function(arguments='{"location": "北京", "date": "2025-12-03"}', name='get_weather'), type='function', index=0)], reasoning_content='今天是2025年12月2日,那么明天就是2025年12月3日。现在我需要查询北京2025年12月3日的天气。调用获取天气的工具。')
======== 获取到一轮结果 ========
ChatCompletionMessage(content='根据天气预报,北京明天(2025年12月3日)的天气情况如下:\n- **天气**:有风(Windy)\n- **气温**:-8°C 到 3°C\n\n明天北京气温较低,且风力较大,请注意添衣保暖和防风。', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None, reasoning_content='我得到了北京明天的天气信息:刮风,气温在-8°C到3°C之间。现在我可以回答用户的问题了。')
========= 第一个问题的答案是: =========
根据天气预报,北京明天(2025年12月3日)的天气情况如下:
- **天气**:有风(Windy)
- **气温**:-8°C 到 3°C

明天北京气温较低,且风力较大,请注意添衣保暖和防风。
======== 获取到一轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_LqVNMPeqw8tiksiJiUiVthfn', function=Function(arguments='{"location": "北京", "date": "2025-12-03"}', name='get_weather'), type='function', index=0)], reasoning_content='用户询问北京12月3日的天气情况。之前用户问过“北京明天的天气如何?”,我们当时获取的日期是2025年12月2日,所以明天就是12月3日。当时我们调用了get_weather工具,获取的天气信息是“Windy -8~3°C”。现在用户再次询问“北京12月3日的天气如何?”,这可能意味着用户想确认这个信息,或者可能想了解其他细节,比如湿度、降水概率等。但我们的工具只能提供基本的天气和温度信息,没有更详细的数据。我们需要再次调用get_weather工具,因为日期是明确的“2025年12月3日”。虽然我们之前已经获取过这个信息,但为了确保信息的准确性和及时性,最好还是重新调用一次。工具应该能返回相同的结果。那么,我们需要再次调用get_weather工具,参数是location为“北京”,date为“2025-12-03”。调用吧。')
======== 获取到一轮结果 ========
ChatCompletionMessage(content='根据最新的天气预报,北京2025年12月3日的天气情况如下:\n\n**天气**:有风(Windy)  \n**气温**:-8°C ~ 3°C\n\n明日北京气温较低,风力较大,请注意做好防寒保暖和防风措施。', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None, reasoning_content='工具返回了结果:“Windy -8~3°C”。这和之前的信息一致。我可以这样回答:根据天气预报,北京2025年12月3日(明天)的天气情况如下:有风,气温在-8°C到3°C之间。考虑到气温较低且有风,建议用户注意保暖和防风。我还应该提到,这是最新的天气预报信息。那么,开始回答。')
========= 第二个问题的答案是: =========
根据最新的天气预报,北京2025年12月3日的天气情况如下:

**天气**:有风(Windy)  
**气温**:-8°C ~ 3°C

明日北京气温较低,风力较大,请注意做好防寒保暖和防风措施。
View Code

可以看到,同一个问题换了个问法,大模型会结合历史自行思考是否要调用工具,并得出答案。

进一步思考,上面的代码是否可以优化?比如,如果达到了max_iterations,但模型此次仍然返回了工具调用,messages的最后一项对应的就是未经整理的工具返回,我们可能希望模型对工具的返回做出更好的处理。

那么改进一下代码,把max_iterations调整成比之前小一步,因为最后一步思考要用来总结输出。再在达到思考轮次限制后,检测是否断在了工具输出上,如果是,让模型整理目前进展得到答案。

尝试问一个需要思考比较多步的问题:

# 第四步 一次问题的思考、调用工具直到回答过程
def run_turn(messages: list = []) -> str:
    """
    获取一个比较复杂问题的回答,模型经过思考、调用工具、处理工具返回,最终返回文本时,得到答案
    Args:
        query: str 当前问题
        historys: messages[] 历史消息列表,可以为空
    Returns:
        问题的答案,一个字符串
    """
    
    # 循环进行思考和行动
    max_iterations = 3
    step = 0
    while step < max_iterations:
        # 请求得到答案并进行处理
        res_message = send_message(messages)
        print(f'======== 获取到第{step + 1}轮结果 ========')
        print(res_message)
        # 先把得到的消息存入列表
        messages.append(res_message)
        # 分析返回消息类型,是工具调用还是输出文本
        tool_calls = res_message.tool_calls
        if not tool_calls or len(tool_calls) == 0:
            # 没有调用工具,判断为可以返回答案了
            break
        # 调用工具
        for tool in tool_calls:
            try:
                func_name = tool.function.name
                func_id = tool.id
                func_args = json.loads(tool.function.arguments)
                func_res = TOOL_MAP[func_name](**func_args)
            except Exception as e:
                print(f'调用工具{func_name}过程出错:{e}, 参数为:{func_args}')
            # 组装结果为ToolMessage
            messages.append({
                'role': 'tool',
                'tool_call_id': func_id,
                'content': func_res
            })
        step += 1
    
    if step >= max_iterations and messages[-1].get('role', '') == 'tool':
        messages.append({
            'role': 'user',
            'content': '已经达到最大思考轮次,请停止一切工具调用,根据前面的思考以及工具结果,整理出一个答案'
        })
        res_message = send_message(messages)
        messages.append(res_message)
    
    return messages

# 为了节省请求token,在每次问题结束时清空上次的reasoning_content
def clear_reasoning_content(messages: list) -> list:
    for msg in messages:
        if hasattr(msg, 'reasoning_content'):
            msg.reasoning_content = None
    return messages

def main():
    messages = [
        {
            'role': 'system',
            'content': '你是一个会使用工具帮助用户回答问题的助手'
        },
        {
            'role': 'user',
            'content': '昨天,今天,明天,哪一天北京的气温更高?'
        }
    ]
    res_msgs = run_turn(messages)
    print('========= 第一个问题的答案是: =========')
    print(res_msgs[-1].content)
View Code

得到的回复:

======== 获取到第1轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_duFshmwTOHqrlhg4DLEnU5md', function=Function(arguments='{}', name='get_today_date'), type='function', index=0)], reasoning_content='用户想知道北京昨天、今天和明天这三天的温度对比,看看哪一天更暖和。我需要获取北京这三天的天气数据来比较温度。\n\n首先,我需要获取今天的日期,然后计算出昨天和明天的日期。\n\n让我先调用工具获取今天的日期。')
======== 获取到第2轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_phzuBbdBmQcLEffBpFOdMJUI', function=Function(arguments='{"date": "2025-12-01", "location": "北京"}', name='get_weather'), type='function', index=0)], reasoning_content='今天日期是2025-12-02。那么昨天就是2025-12-01,明天是2025-12-03。\n\n现在需要获取北京这三天的天气数据。我会逐一调用天气查询工具。先查昨天的天气:2025-12-01,北京。')
======== 获取到第3轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_aJnb7d0DnUoovVySLNihKFoL', function=Function(arguments='{"date": "2025-12-02", "location": "北京"}', name='get_weather'), type='function', index=0)], reasoning_content='昨天的天气是:有风,温度范围-8到3°C。\n\n现在查今天的天气:2025-12-02,北京。')
========= 第一个问题的答案是: =========
根据查询结果:

**昨天(2025-12-01)** 和 **今天(2025-12-02)** 北京的气温都是 **-8°C 到 3°C**,这两天的气温范围完全一致,没有高低之分。

关于 **明天(2025-12-03)** 的气温,由于工具调用次数限制,我无法查询到最新的天气信息。建议您稍后通过其他方式(如天气应用、网站或重新提问)获取明天的准确预报,以便与昨天和今天进行比较。

**当前结论:** 在已经查询的昨天和今天,北京的气温相同。
View Code

虽然模型总结了之前的信息,并且给出了答复,但进一步思考,我们的代码中已经使用for循环支持了多个工具并行调用,是否可以改进prompt?

messages = [
        {
            'role': 'system',
            'content': """你是一个会使用工具帮助用户回答问题的助手。请遵循以下规则:
            1. 你的目标是高效、准确地解决问题,而非进行无边无际的思考。
            2. 对于简单问题(如事实查询、简单计算),请直接给出答案,或最多进行1-2步推理。
            3. 对于复杂问题,允许你进行多步推理和多次工具调用,但总步骤(推理+工具调用)不应超过3次。
            4. 当即将到达思考上限时,你应优先整合已有信息给出最佳答案,而不是发起新的工具调用。
            5. 每次调用工具前,请明确该工具调用对解决最终问题的必要性。
            6. 对于互相没有依赖关系的工具调用过程,可以并行调用。
            """
        },
        {
            'role': 'user',
            'content': '昨天,今天,明天,哪一天北京的气温更高?'
        }
    ]

看一下返回:

======== 获取到第1轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_okSA1xwLKDkqkrf7Bk016kYO', function=Function(arguments='{}', name='get_today_date'), type='function', index=0)], reasoning_content='用户想比较北京昨天、今天、明天这三天的气温,找出哪一天气温最高。我需要获取北京这三天的天气数据,然后进行比较。\n\n首先,我需要知道今天的日期,这样我才能确定昨天和明天的日期。让我先调用 get_today_date 工具。')
======== 获取到第2轮结果 ========
ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_tq0FWvGoWg0VC8B6gprZyWaG', function=Function(arguments='{"location": "北京", "date": "2025-12-01"}', name='get_weather'), type='function', index=0), ChatCompletionMessageFunctionToolCall(id='call_01_6oqXKxNJMZxAKqJKTp50tFt0', function=Function(arguments='{"location": "北京", "date": "2025-12-02"}', name='get_weather'), type='function', index=1), ChatCompletionMessageFunctionToolCall(id='call_02_HvpPeHqNpYtArhqsQkbJSZvb', function=Function(arguments='{"location": "北京", "date": "2025-12-03"}', name='get_weather'), type='function', index=2)], reasoning_content='今天是2025年12月2日。那么昨天是2025年12月1日,明天是2025年12月3日。\n\n接下来,我需要获取北京这三天(12月1日、12月2日、12月3日)的天气数据。这需要调用三次 get_weather 工具。我可以并行调用,因为它们之间没有依赖关系。\n\n我将调用 get_weather 工具来获取北京这三天的天气数据。')
======== 获取到第3轮结果 ========
ChatCompletionMessage(content='根据查询结果,北京昨天(12月1日)、今天(12月2日)和明天(12月3日)的气温完全相同,都是 **-8°C ~ 3°C**,风力较大(Windy)。因此,这三天的气温一样高。', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None, reasoning_content='三天的天气数据显示都是“Windy -8~3°C”,这意味着气温范围相同:最低气温-8°C,最高气温3°C。所以这三天的气温是一样的。\n\n不过,也许我应该更精确地比较一下。数据显示是相同的温度范围,但也许实际气温会有细微差别。然而,从返回的数据来看,温度范围完全一致。\n\n因此,我的回答是:北京昨天、今天和明天的气温相同,都是-8°C到3°C。我需要把这一点告诉用户。现在我可以给出答案了。')
========= 第一个问题的答案是: =========
根据查询结果,北京昨天(12月1日)、今天(12月2日)和明天(12月3日)的气温完全相同,都是 **-8°C ~ 3°C**,风力较大(Windy)。因此,这三天的气温一样高。

这次在思考轮次限制内,模型也作出了比较靠谱的回答,well done !

最后,如果想让模型强制返回json,可以作如下配置:

response = client.chat.completions.create(
    model="deepseek-chat",
    messages=messages,
    response_format={
        'type': 'json_object'
    }
)

关于DeepSeek官网的教程笔记就到此结束了。

 

posted @ 2025-12-01 23:12  溯光独立开发  阅读(183)  评论(0)    收藏  举报