第 2 课
思考模式、JSON 输出与多轮工具调用
开关思考模式并调强度,读出 reasoning_content 并正确拼接多轮对话,用 JSON Output 拿结构化结果,再写一个思考模式下的多轮工具调用循环。
课程目录第 2 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课按 DeepSeek 官方 思考模式、JSON Output 与 Tool Calls 指南整理(2026-10-22 核对);代码沿用官方示例,只把 key 统一改为从环境变量读取。 目标:学会开关思考模式和调思考强度,读出思考内容并正确拼接多轮对话;让模型输出可直接解析的 JSON;写一个会自己调用函数、能跑多轮的工具循环。
你将得到什么
thinking_demo.py:同一个问题分别用「思考」和「不思考」跑一次json_demo.py:把一段文本解析成严格 JSONtool_loop.py:思考模式下的多轮工具调用循环,以及官方给出的逐轮对照输出
先修:已完成第 1 课,DEEPSEEK_API_KEY 已在环境变量中,已 pip3 install openai。
一、思考模式的开关与强度
两个模型都默认开启思考模式,默认强度 high。OpenAI 格式下用两个参数控制:
| 作用 | 参数 | 取值 |
|---|---|---|
| 开关思考 | thinking | {"type": "enabled"} 或 {"type": "disabled"} |
| 思考强度 | reasoning_effort | low / high / max |
官方还说明了几条容易踩的坑:
- 其他工具传来的强度会被映射:
minimal按low,medium和xhigh按high,ultra按max。 - 思考模式下
temperature、presence_penalty、frequency_penalty不生效(传了不报错,但没有效果)。 - 用 OpenAI 的 Python SDK 时,
thinking要放进extra_body。
新建 thinking_demo.py:
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")
question = [{"role": "user", "content": "9.11 and 9.8, which is greater?"}]
# 1) 思考模式:先输出思维链 reasoning_content,再输出最终回答 content
r1 = client.chat.completions.create(
model="deepseek-flash",
messages=question,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
print("== thinking ==")
print("reasoning_content:", r1.choices[0].message.reasoning_content[:200], "...")
print("content:", r1.choices[0].message.content)
# 2) 非思考模式:直接回答,更快、更省 token
r2 = client.chat.completions.create(
model="deepseek-flash",
messages=question,
extra_body={"thinking": {"type": "disabled"}},
)
print("== non-thinking ==")
print("content:", r2.choices[0].message.content)
print("tokens:", r1.usage.completion_tokens, "vs", r2.usage.completion_tokens)
python3 thinking_demo.py
对照结果(结构如下,具体文字每次不同):
== thinking ==
reasoning_content: (一段英文推理:比较小数部分 0.11 和 0.8 ……)...
content: 9.8 is greater than 9.11 ...
== non-thinking ==
content: 9.8 is greater than 9.11 ...
tokens: (较大的数) vs (较小的数)
最后一行能直观看到:思考模式输出的 token 明显更多。简单问答、分类、改写可以关掉思考省钱;数学、代码、多步规划保持开启。
二、多轮对话:思考内容要不要传回去
思维链在 reasoning_content 字段里,和 content 同级。下一轮要不要把它传回去,取决于请求里有没有 tools 参数:
| 请求情况 | reasoning_content 怎么处理 |
|---|---|
不带 tools | 不需要传回;传了也会被忽略,不拼进上下文 |
带 tools | 之前所有轮次的都必须完整传回,否则返回 400 |
不带工具时,官方示例的第二轮写法是直接把上一轮的 message 追加进去:
messages.append(response.choices[0].message)
messages.append({"role": "user", "content": "How many Rs are there in the word 'strawberry'?"})
response = client.chat.completions.create(
model="deepseek-flash",
messages=messages,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
response.choices[0].message 里已经含有 content、reasoning_content(以及有工具调用时的 tool_calls),直接 append 最省事,带不带工具都不会错。
三、JSON Output:让模型只输出合法 JSON
官方要求同时做到四点:
- 设置
response_format={"type": "json_object"}; - system 或 user 提示词里出现 "json" 这个词,并给出期望格式的示例;
max_tokens设得够大,避免 JSON 被截断;- 官方提示该功能偶尔会返回空 content,可通过调整提示词缓解。
新建 json_demo.py(官方示例):
import json
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")
system_prompt = """
The user will provide some exam text. Please parse the "question" and "answer" and output them in JSON format.
EXAMPLE INPUT:
Which is the highest mountain in the world? Mount Everest.
EXAMPLE JSON OUTPUT:
{
"question": "Which is the highest mountain in the world?",
"answer": "Mount Everest"
}
"""
user_prompt = "Which is the longest river in the world? The Nile River."
messages = [{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}]
response = client.chat.completions.create(
model="deepseek-flash",
messages=messages,
response_format={"type": "json_object"},
)
print(json.loads(response.choices[0].message.content))
python3 json_demo.py
对照结果(官方文档给出的模型输出):
{
"question": "Which is the longest river in the world?",
"answer": "The Nile River"
}
终端里打印的是 Python 字典:{'question': 'Which is the longest river in the world?', 'answer': 'The Nile River'}。如果 json.loads 报错或 content 为空,按第 4 点调整提示词后重试。
四、工具调用:让模型自己决定调哪个函数
模型本身不执行函数,它只返回「想调用哪个函数、参数是什么」,由你的代码执行后把结果以 role: "tool" 消息交回去,直到模型不再要求调用工具、给出最终回答。
下面是官方「思考模式 + 工具调用」示例,两个函数用假数据模拟。新建 tool_loop.py:
import os
import json
from datetime import datetime
from openai import OpenAI
tools = [
{
"type": "function",
"function": {
"name": "get_date",
"description": "Get the current date",
"parameters": {"type": "object", "properties": {}},
},
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather of a location, the user should supply the location and date.",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "The city name"},
"date": {"type": "string", "description": "The date in format YYYY-mm-dd"},
},
"required": ["location", "date"],
},
},
},
]
def get_date_mock():
return datetime.now().strftime("%Y-%m-%d")
def get_weather_mock(location, date):
return "Cloudy 7~13°C"
TOOL_CALL_MAP = {"get_date": get_date_mock, "get_weather": get_weather_mock}
def run_turn(turn, messages):
sub_turn = 1
while True:
response = client.chat.completions.create(
model="deepseek-flash",
messages=messages,
tools=tools,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
# 整条 message 追加回去:带 tools 时 reasoning_content 必须传回
messages.append(response.choices[0].message)
reasoning_content = response.choices[0].message.reasoning_content
content = response.choices[0].message.content
tool_calls = response.choices[0].message.tool_calls
print(f"Turn {turn}.{sub_turn}\n{reasoning_content=}\n{content=}\n{tool_calls=}")
if tool_calls is None: # 没有工具调用 = 已给出最终回答
break
for tool in tool_calls:
tool_function = TOOL_CALL_MAP[tool.function.name]
tool_result = tool_function(**json.loads(tool.function.arguments))
print(f"tool result for {tool.function.name}: {tool_result}\n")
messages.append({"role": "tool", "tool_call_id": tool.id, "content": tool_result})
sub_turn += 1
print()
client = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")
messages = [{"role": "user", "content": "How's the weather in Hangzhou Tomorrow"}]
run_turn(1, messages)
messages.append({"role": "user", "content": "How's the weather in Guangzhou Tomorrow"})
run_turn(2, messages)
python3 tool_loop.py
对照结果(官方文档的样例输出,已删去 Turn 1.3 与 Turn 2.2 回答正文中的部分修饰语;日期是官方运行当天,你看到的是你的「明天」):
Turn 1.1
reasoning_content="The user is asking about the weather in Hangzhou tomorrow. I need to get tomorrow's date first, then call the weather function."
content="Let me check tomorrow's weather in Hangzhou for you. First, let me get tomorrow's date."
tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_kw66qNnNto11bSfJVIdlV5Oo', function=Function(arguments='{}', name='get_date'), type='function', index=0)]
tool result for get_date: 2026-04-19
Turn 1.2
reasoning_content="Today is 2026-04-19, so tomorrow is 2026-04-20. Now I'll call the weather function for Hangzhou."
content=''
tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_H2SCW6136vWJGq9SQlBuhVt4', function=Function(arguments='{"location": "Hangzhou", "date": "2026-04-20"}', name='get_weather'), type='function', index=0)]
tool result for get_weather: Cloudy 7~13°C
Turn 1.3
reasoning_content='The weather result is in. Let me share this with the user.'
content="Here's the weather forecast for **Hangzhou tomorrow (April 20, 2026)**: ..."
tool_calls=None
Turn 2.1
reasoning_content='The user is asking about the weather in Guangzhou tomorrow. Today is 2026-04-19, so tomorrow is 2026-04-20. I can directly call the weather function.'
content=''
tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_00_8URkLt5NjmNkVKhDmMcNq9Mo', function=Function(arguments='{"location": "Guangzhou", "date": "2026-04-20"}', name='get_weather'), type='function', index=0)]
tool result for get_weather: Cloudy 7~13°C
Turn 2.2
reasoning_content='The weather result for Guangzhou is the same as > 本课按 DeepSeek 官方 [接入 Claude Code](https://api-docs.deepseek.com/zh-cn/quick_start/agent_integrations/claude_code)、[Anthropic API](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api)、[网页搜索与抓取](https://api-docs.deepseek.com/zh-cn/guides/search) 与 [V4.1-Flash-1017 发布公告](https://api-docs.deepseek.com/zh-cn/news/news261017) 整理(2026-10-22 核对);Anthropic SDK 与 Search 接口的报错输出为 2026-10-22 实测(anthropic 2.0.0)。
> 目标:用 DeepSeek 驱动 Claude Code 写代码;用 Anthropic SDK 调 DeepSeek;再用官方 Search / Fetch 接口给自己的应用加上「先搜索、再回答、附来源」的能力。
## 你将得到什么
- 一组可直接粘贴的 Claude Code 环境变量(Linux / macOS 与 Windows 各一份)
- `anthropic_call.py`:用 Anthropic SDK 调 `deepseek-flash`
- `search_answer.py`:调 Search 接口拿结果,再让 `deepseek-flash` 带编号引用作答
- 一张 Search / Fetch 计费与限速速查表
先修:已完成第 1 课;本机有 Node.js 18+(Windows 还需 Git for Windows)。
## 一、把 DeepSeek 接进 Claude Code
DeepSeek 提供 Anthropic 兼容地址 `https://api.deepseek.com/anthropic`,Claude Code 只要改环境变量就能用 DeepSeek 模型。
**1. 安装 Claude Code**(已安装可跳过):
```bash
npm install -g @anthropic-ai/claude-code
claude --version
对照结果:打印出版本号即安装成功。
2. 配置环境变量(官方推荐配置,<your DeepSeek API Key> 换成你的 key)。Linux / macOS:
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_AUTH_TOKEN=<your DeepSeek API Key>
export ANTHROPIC_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-flash
export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-flash
export CLAUDE_CODE_EFFORT_LEVEL=max
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=786432
Windows PowerShell:
$env:ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
$env:ANTHROPIC_AUTH_TOKEN="<your DeepSeek API Key>"
$env:ANTHROPIC_MODEL="deepseek-flash[1m]"
$env:ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-flash[1m]"
$env:ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-flash[1m]"
$env:ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-flash"
$env:CLAUDE_CODE_SUBAGENT_MODEL="deepseek-flash"
$env:CLAUDE_CODE_EFFORT_LEVEL="max"
$env:CLAUDE_CODE_AUTO_COMPACT_WINDOW="786432"
每一行的作用:
| 变量 | 作用 |
|---|---|
ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN | 请求改发到 DeepSeek,并用 DeepSeek 的 key 鉴权 |
ANTHROPIC_MODEL 与三个 DEFAULT_*_MODEL | 主模型和 Opus / Sonnet / Haiku 档位都用 deepseek-flash;[1m] 表示使用 1M 上下文 |
CLAUDE_CODE_SUBAGENT_MODEL | 子智能体也用 deepseek-flash |
CLAUDE_CODE_EFFORT_LEVEL | 思考强度设为 max |
CLAUDE_CODE_AUTO_COMPACT_WINDOW | 自动压缩对话所参照的上下文窗口(786432 = 768K token) |
这些 export 只对当前终端有效;想长期使用,把它们写进 ~/.bashrc 或 ~/.zshrc。
3. 进入项目启动:
cd /path/to/my-project
claude
进入后可以先让它做一件只读的小事验证连通,例如输入:
用三句话概括这个仓库的文件夹结构,不要修改任何文件。
对照结果:Claude Code 读取项目文件后用中文给出概括。到 DeepSeek 开放平台查看用量,能看到这次会话产生的 deepseek-flash 调用。
两个要知道的细节:
- 模型映射:如果你不改模型变量、仍传 Claude 模型名,DeepSeek 会自动映射:
claude-opus开头的映射到deepseek-v4-pro(按 V4 Pro 计价),claude-sonnet/claude-haiku开头的映射到deepseek-flash。上面的配置把所有档位都指向 Flash,费用最低。 - 联网搜索:在 Claude Code 里提问需要联网时,模型会调用 Web Search,由 DeepSeek 的搜索接口完成。官方公告写明:这部分 Search / Fetch 调用目前不计费,北京时间 2026 年 11 月 1 日 00:00 起按次收费(另有总结搜索结果产生的 token 费用)。
二、用 Anthropic SDK 调 DeepSeek
已经在用 Anthropic SDK 的应用,改两个环境变量就能切到 DeepSeek:
pip3 install anthropic
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_API_KEY=$DEEPSEEK_API_KEY
新建 anthropic_call.py(官方示例):
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="deepseek-flash",
max_tokens=1000,
system="You are a helpful assistant.",
messages=[
{
"role": "user",
"content": [{"type": "text", "text": "Hi, how are you?"}],
}
],
)
print(message.content)
python3 anthropic_call.py
对照结果:打印一个内容块列表(message.content),其中 type='text' 的 TextBlock 里就是模型的回答。
key 不对时(2026-10-22 实测,最后一行):
anthropic.AuthenticationError: Error code: 401 - {'error': {'message': 'Authentication Fails, Your api key: ****-key is invalid (request_id: 713a720a-be32-4e14-946e-cfb797b2ebb2)', 'type': 'authentication_error', 'param': None, 'code': 'invalid_request_error'}}
注意:model 写不以 claude 开头、又不是 DeepSeek 模型名的值,会直接返回 400。Anthropic 格式下调思考强度用 output_config,例如 {"effort": "high"}。
三、Search 接口:用 DeepSeek 自研搜索引擎搜网页
V4.1-Flash-1017 发布时,DeepSeek 把网页端一直在用的自研搜索引擎开放为 API。鉴权和对话接口相同,都是 Bearer key。
curl https://api.deepseek.com/v1/tools/search \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "DeepSeek V4 模型 发布",
"count": 5,
"need_content": true
}'
对照结果(官方文档示例,results 数组每项结构如下;content 只在 need_content 为 true 时有值):
{
"results": [
{
"rerank_score": 2.4,
"title": "DeepSeek V4 模型发布",
"url": "https://example.com/deepseek-v4",
"snippet": "DeepSeek V4 已上线,支持工具调用与长上下文。",
"summary": "DeepSeek 发布了 V4 模型。此次发布改进了工具调用与长上下文能力,适合 Agent 工作流。",
"content": "DeepSeek V4 现已正式上线……",
"site_name": "Example News",
"site_icon": "https://example.com/favicon.ico",
"date": "2026-06-01T00:00:00+00:00",
"authority": 3
}
]
}
| 字段 | 用法 |
|---|---|
snippet | 100–200 字短摘要,适合前端展示 |
summary | 600–800 字长摘要,适合直接喂给模型 |
rerank_score | 0–3 分,≥1.5 为优质结果 |
authority | 0–4(-1 表示未知),≥3 为极权威来源 |
date | UTC 的 RFC 3339 时间,未知时为 null |
请求参数:query 必填(1–128 个字符);count 为 1–50,默认 10;need_content 默认 false。query 里可以直接用搜索语法,常用的有:
DeepSeek site:github.com OR site:huggingface.co
DeepSeek -site:example.com
DeepSeek lang:zh,en
DeepSeek after:2026-01-01
DeepSeek "精确短语"
用假 key 调用的样子(2026-10-22 实测,HTTP 401):
{"error":{"message":"Authentication Fails, Your api key: ****-key is invalid (request_id: eff7c3cb-f7ed-47c7-bff8-d6f7e369d20c)","type":"authentication_error","param":null,"code":"invalid_request_error"}}
四、Fetch 接口:把一个网页抓成 Markdown
curl https://api.deepseek.com/v1/tools/fetch \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://api-docs.deepseek.com/zh-cn/guides/search"}'
对照结果(结构同官方示例;下面的 title 与 content 开头按该页面实际内容示意,content 是整页 Markdown 正文):
{
"title": "网页搜索与抓取 | DeepSeek API Docs",
"content": "# 网页搜索与抓取\n\nSearch 接口和 Fetch 接口为 Agent 与 RAG 应用提供实时网页内容……"
}
url 必须是含 host 的 http/https 地址,不能带用户名密码。抓不到可用正文时返回 404,被安全策略拒绝时返回 403;这两种属于不改请求就不要重试的错误。
五、组合起来:先搜索,再带引用作答
下面把 Search 接口和第 1 课的对话接口串起来:先搜 5 条结果,把 summary 编号后交给 deepseek-flash,要求回答时用 [1]、[2] 标注来源。新建 search_answer.py:
import os
import sys
import requests
from openai import OpenAI
KEY = os.environ["DEEPSEEK_API_KEY"]
question = sys.argv[1] if len(sys.argv) > 1 else "DeepSeek V4.1 Flash 1017 有哪些更新?"
# 1) 搜索:拿 5 条结果的长摘要
r = requests.post(
"https://api.deepseek.com/v1/tools/search",
headers={"Authorization": f"Bearer {KEY}"},
json={"query": question, "count": 5, "ori_question": question},
timeout=60,
)
r.raise_for_status()
results = r.json()["results"]
sources = "\n\n".join(
f"[{i}] {x['title']}\n{x['url']}\n{x['summary']}" for i, x in enumerate(results, 1)
)
# 2) 作答:只依据搜索结果,用编号标注来源
client = OpenAI(api_key=KEY, base_url="https://api.deepseek.com")
resp = client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "system", "content": "只根据给出的搜索结果回答,用中文,每个事实后用 [编号] 标注来源;结果里没有的就说不知道。"},
{"role": "user", "content": f"问题:{question}\n\n搜索结果:\n{sources}"},
],
extra_body={"thinking": {"type": "disabled"}},
)
print(resp.choices[0].message.content)
print("\n来源:")
for i, x in enumerate(results, 1):
print(f"[{i}] {x['url']}")
pip3 install requests openai
python3 search_answer.py "DeepSeek V4.1 Flash 1017 有哪些更新?"
对照结果(结构如下;具体内容取决于当时的搜索结果):
DeepSeek V4.1 Flash 已更新到 1017 版本,网页端、App 与 API 同步切换 [1]……同时开放了 Search 与 Fetch 接口,价格为 5 元 / 千次 [2]……
来源:
[1] https://api-docs.deepseek.com/zh-cn/news/news261017
[2] (第 2 条搜索结果的网址)
这里用 ori_question 把用户原问题一起传给搜索接口,官方说明这有助于理解搜索意图;作答时关掉思考模式,是因为这一步主要是归纳,不需要长推理。
六、计费与限速速查
| 项目 | Search | Fetch |
|---|---|---|
| 价格 | 5 元 / 千次成功请求 | 5 元 / 千次成功请求 |
| 账号 QPS 上限 | 100 | 25(另有目标域名限速) |
| 什么时候不计费 | 请求失败、无结果、超时 | 请求失败、无正文、超时 |
- 只有 HTTP 200 且有结果的请求计费;
count多少不影响单价。 - 遇到 408、429、5xx 等待后重试;402 先充值;其他 4xx 不改请求就不要重试。
- Claude Code / Codex / DeepSeek Harness 里由服务端自动发起的搜索,2026 年 11 月 1 日 00:00(北京时间)起也按上表计费。
本课检查清单
- Claude Code 用 DeepSeek 完成了一次只读任务
-
anthropic_call.py打印出了 TextBlock - Search curl 返回了
results数组,并能说出snippet与summary的区别 -
search_answer.py输出了带 [编号] 的回答和来源列表
学完三课,你已经能用 DeepSeek 完成「直接调用 → 思考与工具调用 → 接入编码 Agent 与联网搜索」这一整套流程。价格和模型名以后还会变,动手前先看一眼官方「模型 & 价格」页。Hangzhou. Let me share this with the user.' content="Here's the weather forecast for Guangzhou tomorrow (April 20, 2026): ..." tool_calls=None
读这段输出要看三件事:
- Turn 1 里模型先调 `get_date` 拿到今天日期,再调 `get_weather`,说明它会自己规划调用顺序;
- Turn 2 没有再调 `get_date`,因为第一轮的推理已经在上下文里(这正是带 tools 时必须传回 `reasoning_content` 的原因);
- `tool_calls=None` 时循环结束,`content` 就是最终回答。
五、两个进阶开关(按需)
- strict 模式(Beta):要求工具参数严格符合你定义的 JSON Schema。用法是把
base_url改成https://api.deepseek.com/beta,并给每个 function 加"strict": true。思考和非思考模式都支持。 - 非思考模式调工具:只把
extra_body改成{"thinking": {"type": "disabled"}},其余写法相同,适合对延迟敏感、逻辑简单的工具调用。
本课检查清单
-
thinking_demo.py两种模式都给出了正确答案,且能看出 token 差异 -
json_demo.py打印出含question和answer的字典 -
tool_loop.py跑出了与对照结果同样结构的 Turn 1.1–1.3、2.1–2.2 - 能说清楚:什么情况下必须把
reasoning_content传回
下一课:把 DeepSeek 接进 Claude Code 当编码 Agent 的大脑,再用官方 Search / Fetch 接口给自己的应用加上联网能力。
