DeepSeek API(兼容OpenAI)
DeepSeek、Qwen、Kimi 这些国内顶级模型都是兼容 OpenAI 的 API 格式的。
- 首先,OpenAI 先发制人,其制定的 API 在最开始就占据了市场的半壁江山,被全球开发者广泛接受。
- 其他大模型厂家兼容 OpenAI 的 API 格式,可以降低开发者的学习和迁移成本,更方便从 OpenAI 生态中抢人。
我这里使用 DeepSeek-v4-flash 模型来进行接口调用,这应该是最便宜的模型了。
1.跑通 DeepSeek API(非流式)
既然要跑通 DeepSeek API,第一件事当然是看官方文档,学习一下官方的操作细节。
官方文档地址:首次调用 API | DeepSeek API Docs,在这个页面中下拉到最底部,就可以看到快速入门的示例了。

这里提供了 python 和 node.js 的示例代码,还有直接通过 curl 进行接口调用的方式。
我这里就直接用 python 调用接口了,因为文档里提供的 python 和 node.js 代码都要引入对应的库函数,在 java 里面就是引入依赖。
比如:在 python 示例代码里面要用到 openai 这个库,这个库可不是 python 自带的,是需要通过 pip 安装的。
# Please install OpenAI SDK first: `pip3 install openai`
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}}
)
print(response.choices[0].message.content)
但是我不想依赖这些依赖,还是用 request 调用接口吧。
已知通过 curl 可以直接调用 https://api.deepseek.com/chat/completions 这个接口和所需的参数,接下来就是看图说话了。
这里解释一下相关参数:
- "thinking": {"type": "enabled"} 是用来控制 AI 模型是否返回思考过程的,而且是默认开启的,就算不写也是开启的,要手动改成 disabled 才能关闭。
- "reasoning_effort": "high" 是控制思考强度的,有两个档位可选:high 和 max,max > high
- "stream": False,接口是同步返回结果还是流式返回结果,我这里先由浅入深,就先同步返回
这都是官方文档说的,我不生产文字,我只是这些文字的搬运工。

import requests
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-aef75cba11c94fb2be77e046f8b41739"
}
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "你是谁?"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": False
}
post = requests.post(url, headers=headers, json=payload)
# 打印接口返回的数据
print(post.json())
post.close()
post.json() 返回的是 json 字符串,长这个样子:

这样不便于阅读啊,没关系,网上一堆在线格式化工具,直接复制粘贴上去格式化就可以了。
格式化之后的结构如下:
{
'id': '81c65c60-e25c-4f6b-a848-af81f813d3e1',
'object': 'chat.completion',
'created': 1784880697,
'model': 'deepseek-v4-flash',
'choices': [
{
'index': 0,
'message': {
'role': 'assistant',
'content': '你好!我是DeepSeek,由深度求索公司创造的智能助手。我能够处理各种问题,无论是文本对话、文件上传,还是阅读链接内容,我都可以胜任。目前,我专注于文字处理,不支持多模态识别,但可以对上传的图片中的文字进行读取和理解。我的知识截止日期是2025年5月,并且我支持联网搜索功能(需要手动开启)。最重要的是,我完全免费使用!有什么可以帮你的吗?😊',
'reasoning_content': '嗯,用户问了一个简单的自我介绍问题。这个问题很直接,就是想知道我的身份和功能。我需要清晰说明自己是什么、能做什么。\n\n我是DeepSeek,由深度求索公司创造的AI助手。应该简明扼要地介绍核心特点:纯文本对话、文件处理能力、免费使用、有知识截止日期和上下文长度限制。同时可以用友好的语气表达愿意提供帮助的态度。\n\n想到了用“你好”开头,然后说明身份和基本能力,最后以开放式的提问结束,这样既回答了问题又促进了进一步交流。'
},
'logprobs': None,
'finish_reason': 'stop'
}
],
'usage': {
'prompt_tokens': 10,
'completion_tokens': 211,
'total_tokens': 221,
'prompt_tokens_details': {
'cached_tokens': 0
},
'completion_tokens_details': {
'reasoning_tokens': 112
},
'prompt_cache_hit_tokens': 0,
'prompt_cache_miss_tokens': 10
},
'system_fingerprint': 'fp_8b330d02d0_prod0820_fp8_kvcache_20260402'
}
在 choices 里面的 message 就有发送消息的角色:assistant,消息内容:content 以及 AI 模型的思考内容:reasoning_content。
如果想直接获取模型的回答或者思考过程,可以修改打印代码。
post = requests.post(url, headers=headers, json=payload)
json_obj = post.json()
print("content:", json_obj["choices"][0]["message"]["content"])
print("reasoning_content:", json_obj["choices"][0]["message"]["reasoning_content"])
post.close()
2.跑通 DeepSeek API(流式输出)
当然,我们平时使用 AI,AI 的返回都是流式的。
因为流式返回的数据比非流式有很强的反馈。
- 如果是非流式返回,就相当于 java Thread 类的 join 方法,是同步等待,接口没有返回数据,用户就得不到反馈。如果 AI 需要思考 1 分钟,那用户就要对着屏幕上面的加载动画等一分钟。
- 如果是流式返回,即使 AI 还没有完全给出答案,但是可以让用户看到一部分产出。对于用户来讲,按下鼠标的下一秒就能得到反馈,更能吸引用户,留住用户。
而且流式返回对时间的利用是非常高效的。
- 用户使用产品是为了解决问题,从这个角度看,一轮对话的耗时 != AI 模型返回的时间,还要计算用户解决问题所花的时间。
- 如果是非流式返回,一轮对话的耗时 = AI 模型返回的时间 + 用户理解 AI 返回的数据的时间。
- 如果是流式返回,AI 模型一边在输出,用户同时就可以阅读并理解 AI 返回的结果,AI 输出和用户理解这两个阶段是同时进行的,就类似于流水线架构,可以提高时间的利用率。
那说了这么多不废的话,到底怎么实现流式调用呢?
import requests
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer sk-aef75cba11c94fb2be77e046f8b41739"
}
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "你是谁?"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
with requests.post(url, headers=headers, json=payload) as r:
for line in r.iter_lines():
# 不解码全是英文字符
decode_str = line.decode("utf-8")
if decode_str:
# data: {"id":"...","choices":[{"delta":{"content":"你"}}]}
# data: {"id":"...","choices":[{"delta":{"content":"好"}}]}
# data: [DONE]
if decode_str.startswith('data: '):
# 去掉前面的 "data: " (注意这里包含空格,长度为 6)
json = decode_str[6:]
# strip=trim
if json.strip() == '[DONE]':
break
else:
print(json)
把 payload 里面的 stream 改成 True,这样 post 函数返回的就不是一次性数据了,而是可以迭代的字节流,也可以理解成将一份数据拆成多块,每次返回一小块。
for line in r.iter_lines() 里面的 line 就是一块数据,那这块数据长什么样呢?我们可以直接 print(line) 看一下。
b'data: {"id":"0aac7d59-56d9-4f47-883a-b8564e710aaa","object":"chat.completion.chunk","created":1784883275,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":""},"logprobs":null,"finish_reason":null}]}'
b''
b'data: {"id":"0aac7d59-56d9-4f47-883a-b8564e710aaa","object":"chat.completion.chunk","created":1784883275,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"\xe5\x97\xaf"},"logprobs":null,"finish_reason":null}]}'
b''
省略100万字
b'data: {"id":"0aac7d59-56d9-4f47-883a-b8564e710aaa","object":"chat.completion.chunk","created":1784883275,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"","reasoning_content":null},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":129,"total_tokens":139,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":81},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":10}}'
b''
b'data: [DONE]'
b''
这里开头的 b 表示这是一个字节序列,所以我们看到的 content 没有中文,而是字符的字节表示格式。
所以需要用 decode_str = line.decode("utf-8") 解码得到中文,那这个时候是什么样子呢?
data: {"id":"99cb514e-171f-49bc-b374-3ed94b8ebc19","object":"chat.completion.chunk","created":1784883566,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"","reasoning_content":null},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":117,"total_tokens":127,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":68},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":10}}
data: [DONE]
现在存在空行,同时有 data: 作为前缀,如果 data: [DONE] 表示输出结束。
所以要去掉空行,去掉 data: ,得到 data: 后面的 json。
with requests.post(url, headers=headers, json=payload) as r:
for line in r.iter_lines():
# 不解码全是英文字符
decode_str = line.decode("utf-8")
if decode_str:
# data: {"id":"...","choices":[{"delta":{"content":"你"}}]}
# data: {"id":"...","choices":[{"delta":{"content":"好"}}]}
# data: [DONE]
if decode_str.startswith('data: '):
# 去掉前面的 "data: " (注意这里包含空格,长度为 6)
json = decode_str[6:]
# strip=trim
if json.strip() == '[DONE]':
break
else:
print(json)
现在打印的每块数据就是这样了,可以在每次循环中拼接 content 和 reasoning_content。

3.tools 调用 / function calling
现在我问 DeepSeek-v4-flash 一个简单的问题吧:"今天广州的天气怎么样?",它返回的结果是:
'message': {
'role': 'assistant',
'content': '抱歉,我无法直接查询实时天气信息。建议您打开联网搜索功能,或使用手机上的天气应用获取广州今日的准确天气情况。',
'reasoning_content': '我们查询广州今天的天气。作为AI助手,我没有实时联网功能,但可以基于常见知识回答或提示需要联网。为了提供准确信息,建议用户开启联网搜索。如果无法联网,我可以给出一般性建议。'
}
这个故事告诉我们,AI 模型是不能实时获取数据的,没有联网能力。
其实这个时候,我们应该就要发现,我们一直是面向接口开发,所谓的 AI 模型其实就是一个接口。调用 DeepSeek API 和访问 www.baidu.com 没有区别,都是我们构建输入参数,调用接口,从接口中获取返回的输出数据。
那为什么 AI 模型不能实时联网呢?因为这个接口就不支持这个功能啊,就像我们访问 www.baidu.com 也只能访问百度主页,不能获取广州的天气啊。
所以现在对 AI 模型的认知很明确了,AI 模型只能根据自己拥有的能力为我们提供服务,它不支持的功能就无法提供服务。
但是我就是想它能够查询天气,我就要我就要,人家就是要嘛。
这个时候就要回到 "面向接口" 模型了:输入 -> 黑盒 -> 输出,既然 AI 模型不能获取天气信息,我们就要自己获取天气数据,然后在输入中把天气信息传进去。
大师,我悟了,原来人工智能 = 人工 + 智能。
只不过,获取天气信息这一步,不用我们自己做,我们可以编写一个函数来实现这个功能,实现一次编写,自动运行,不用手动运行。
这个就是 tools 工具调用,当然也可以说是 function calling,这两种说法其实是同一个行为,具体可以看这里:什么叫function calling, 和tools调用有啥区别? - 知乎。
既然我们要用 tools,就先定义一个工具吧。
def get_weather(city):
if city == "广州":
return "上午晴朗无云,下午2:00-4:00有暴雨"
else:
return "最近三天天气以高温晴热为主,气温上升至35℃以上"
现在我们知道了,有一个函数可以获取天气的信息,但是 AI 模型可不知道,我们需要把这个函数写进输入参数里面去。
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "广州今天天气怎么样?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取某个城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto",
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
在 payload 中添加 tools 数组,既然 tools 是一个数组,就说明 tools 里面可以声明多个 tool,那 AI 模型一次性就可以调用多个函数了,这个就是下一个标题的内容了,这里先专注于单个函数的调用。
function 里面的 name 是函数的名称,description 是对函数功能的描述,越详细越好,AI 模型要调用这个函数总得先知道这个函数能干嘛吧。
parameters 就是这个函数的输入参数,type = object 表示这个参数是一个对象,properties 就是这个对象具体的属性了,其中 city 是字符串,用来表示城市的名字,required 表示这个函数必须传入的参数,如果没有传入规定的参数就不能正常工作。(python 是支持默认参数的)
这里我们增加了一个新属性:tool_choice,tool_choice 有三个取值。

那我们的代码怎么写呢?不急,先看接口返回什么数据。
with requests.post(url, headers=headers, json=payload) as r:
for line in r.iter_lines():
decode_str = line.decode("utf-8")
if decode_str:
if decode_str.startswith('data: '):
json = decode_str[6:]
if json.strip() == '[DONE]':
break
else:
print(json)
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":""},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"用户"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"想知道"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"广州"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"今天的"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"天气"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"如何"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"。"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"我可以"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"使用"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"get"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"_"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"weather"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"工具"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"来"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"获取"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"广州"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"的"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"天气"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"信息"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":null,"reasoning_content":"。"},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"广州","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"今天","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"天气","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"怎么样","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"?","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"我来","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"查","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"一下","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"!","reasoning_content":null},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_00_xs2snU0Ww13qSYI5r8jS7479","type":"function","function":{"name":"get_weather","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{"}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":": "}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"广州"}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]}
{"id":"60be1311-fca5-4d63-b2d6-d4d03a0c1cd1","object":"chat.completion.chunk","created":1784892241,"model":"deepseek-v4-flash","system_fingerprint":"fp_8b330d02d0_prod0820_fp8_kvcache_20260402","choices":[{"index":0,"delta":{"content":"","reasoning_content":null},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":288,"completion_tokens":73,"total_tokens":361,"prompt_tokens_details":{"cached_tokens":256},"completion_tokens_details":{"reasoning_tokens":20},"prompt_cache_hit_tokens":256,"prompt_cache_miss_tokens":32}}
我们可以看到,AI 模型先输出了思考过程:

然后输出了回复内容:

最后通过 tool_calls 调用了 get_weather 函数,传入的参数是 {"city": "广州"}。

但是,它只是说要调用 get_weather 函数,它自己能调用吗?
当然不能,所以这个函数需要我们自己调用,然后把这个函数的调用结果传入到输入参数中,再次调用同一个接口。
接下来上代码:
import requests
import json
def get_weather(city):
if city == "广州":
return "上午晴朗无云,下午2:00-4:00有暴雨"
else:
return "最近三天天气以高温晴热为主,气温上升至35℃以上"
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-aef75cba11c94fb2be77e046f8b41739"
}
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "广州今天天气怎么样?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取某个城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto",
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
while True:
reasoning = ""
content = ""
tool_call = {}
with requests.post(url, headers=headers, json=payload) as r:
for line in r.iter_lines():
decode_str = line.decode("utf-8")
if decode_str:
if decode_str.startswith('data: '):
json_str = decode_str[6:]
if json_str.strip() == '[DONE]':
break
else:
delta = json.loads(json_str)["choices"][0]["delta"]
# 思考过程
if "reasoning_content" in delta and delta["reasoning_content"]:
reasoning += delta["reasoning_content"]
# 回答
if "content" in delta and delta["content"]:
content += delta["content"]
# 工具调用
if "tool_calls" in delta:
t = delta["tool_calls"][0]
if "id" in t:
tool_call["id"] = t["id"]
if "function" in t:
fn = t["function"]
if "name" in fn:
if "name" in tool_call:
tool_call["name"] += fn["name"]
else:
tool_call["name"] = fn["name"]
if "arguments" in fn:
if "arguments" in tool_call:
tool_call["arguments"] += fn["arguments"]
else:
tool_call["arguments"] = fn["arguments"]
if not tool_call:
print("思考过程:", reasoning)
print("回答:", content)
break
else:
function_name = tool_call["name"]
result = None
if function_name == "get_weather":
input_json = json.loads(tool_call["arguments"])
result = get_weather(input_json["city"])
payload["messages"].append(
{
"role": "assistant",
"reasoning_content": reasoning,
"content": content,
"tool_calls": [
{
"id": tool_call["id"],
"type": "function",
"function": {
"name": tool_call["name"],
"arguments": tool_call["arguments"],
},
}
],
})
payload["messages"].append({"role": "tool", "tool_call_id": tool_call["id"], "content": result})
print("思考过程:", reasoning)
print("回答:", content)
print("tool调用:", tool_call)
print("tool返回结果:", result)
哎呀,这段代码真的是又臭又长,如果不是我写的我就要骂人了。
首先,在 while 循环前面的应该看得懂,我就不多说了。
就从 while 循环开始,为什么需要加 while 循环呢?
因为 AI 模型需要调用工具啊。以前我和 AI 模型的交互是一问一答,我们很清楚 AI 模型只返回一次。但是有了工具之后,AI 模型就可以一直不给出最终答案,一直调用工具了,比如:
- 第一轮:输入 abc,AI 模型思考后,我需要调用 A 工具
- 第二轮:执行 A 工具把数据给到 AI 模型,AI 模型思考后,我需要调用 B 工具和 C 工具
- 第三轮:AI 模型获得 B 工具和 C 工具的执行结果,我需要调用 AA 工具、BB 工具和 CC 工具
- ...
- 不知道多少轮:AI 模型:我需要调用 AAAAAA 工具和 BBBBBB 工具
我们已经无法确定 AI 模型到底要进行多少轮对话才能给出答案,所以需要用 while 循环来一直调用接口。
一次循环就是一次调用,是可以获取这次调用流式返回的全部数据的,所以需要用 reasoning、content、tool_call = {} 来保存这次调用的思考过程、回答和工具调用。
tool_call 是一个对象,需要提供 id、name、arguments 三个属性:
- id:用来标识一次工具调用,多次调用同一个工具会产生多个不同的 id
- name:被调用的函数的名字
- arguments:函数需要的参数
delta = json.loads(json_str)["choices"][0]["delta"]
# 思考过程
if "reasoning_content" in delta and delta["reasoning_content"]:
reasoning += delta["reasoning_content"]
# 回答
if "content" in delta and delta["content"]:
content += delta["content"]
[
{
"index": 0,
"delta": {
"tool_calls": [
{
"index": 0,
"id": "call_00_3I1zLahFxTEznCBAbT1l9208",
"type": "function",
"function": {
"name": "get_weather",
"arguments": ""
}
}
]
},
"logprobs": null,
"finish_reason": null
}
]
}{
"id": "8b6ef8f6-511a-485d-be5e-31c5fd935f77",
"object": "chat.completion.chunk",
"created": 1784774383,
"model": "deepseek-v4-flash",
"system_fingerprint": "fp_8b330d02d0_prod0820_fp8_kvcache_20260402",
"choices": [
{
"index": 0,
"delta": {
"tool_calls": [
{
"index": 0,
"function": {
"arguments": "{"
}
}
]
},
"logprobs": null,
"finish_reason": null
}
]
}{
"id": "8b6ef8f6-511a-485d-be5e-31c5fd935f77",
"object": "chat.completion.chunk",
"created": 1784774383,
"model": "deepseek-v4-flash",
"system_fingerprint": "fp_8b330d02d0_prod0820_fp8_kvcache_20260402",
"choices": [
{
"index": 0,
"delta": {
"tool_calls": [
{
"index": 0,
"function": {
"arguments": "\""
}
}
]
},
"logprobs": null,
"finish_reason": null
}
]
}
在这里,delta 是什么呢?自己看 json 数据结构好吧。
if "reasoning_content" in delta and delta["reasoning_content"]:因为 delta 不一定有 reasoning_content 这个属性,就算有也可能为空,所以只有 reasoning_content 存在且不为空才用 += 拼接起来。
if "tool_calls" in delta:
t = delta["tool_calls"][0]
if "id" in t:
tool_call["id"] = t["id"]
if "function" in t:
fn = t["function"]
if "name" in fn:
if "name" in tool_call:
tool_call["name"] += fn["name"]
else:
tool_call["name"] = fn["name"]
if "arguments" in fn:
if "arguments" in tool_call:
tool_call["arguments"] += fn["arguments"]
else:
tool_call["arguments"] = fn["arguments"]
如果 delta 里面有 tool_calls 属性,获取 tool_calls 属性。
如果 tool_calls 里面有 id 把 id 记录到 tool_call 这个对象里面。
如果 tool_calls 里面有 function,获取 function。
如果 function 里面有 name 和 arguments,就记录到 tool_call 这个对象里面。
那这段代码是什么意思?
if "name" in tool_call:
tool_call["name"] += fn["name"]
else:
tool_call["name"] = fn["name"]
其实 python 很灵活的,tool_call = {},是一个空对象,但是 tool_call["name"] = "李华" 是不报错的,tool_call = {"name": "李华"}。
但是,tool_call = {},print(tool_call["name"]) 会报错,因为 tool_call 没有这个属性。
所以啊,我的 tool_call 是空对象,如果 tool_call 里面没有 name 这个属性就新增属性,如果已经有了就拼接字符串咯。
if not tool_call:
print("思考过程:", reasoning)
print("回答:", content)
break
for 循环结束之后,如果 tool_call 还是空对象,说明这次没有进行工具调用,是最终回答,所以可以结束循环了。这是官方文档说的,可不是我的一家之言。

那如果有工具调用呢?那就要调用本地函数获取结果了。
else:
function_name = tool_call["name"]
result = None
if function_name == "get_weather":
input_json = json.loads(tool_call["arguments"])
result = get_weather(input_json["city"])
payload["messages"].append(
{
"role": "assistant",
"reasoning_content": reasoning,
"content": content,
"tool_calls": [
{
"id": tool_call["id"],
"type": "function",
"function": {
"name": tool_call["name"],
"arguments": tool_call["arguments"],
},
}
],
})
payload["messages"].append({"role": "tool", "tool_call_id": tool_call["id"], "content": result})
print("思考过程:", reasoning)
print("回答:", content)
print("tool调用:", tool_call)
print("tool返回结果:", result)
从 tool_call 里面获取函数名,因为 arguments 是一个 json 字符串,所以要先将字符串转成 json 然后拿到里面的 city 的值,调用函数把结果存入 result 中。
接下来就要在 message 中插入一条新消息了,这条新消息要包含 reasoning_content 属性和 tool_calls 属性。
在 tool_calls 中需要说明自己这次调用了哪个工具,id 是多少,名字是什么,参数是什么。
并且,还要继续插入消息,这里的消息的 role 就是 tool 了,因为我们需要传入工具调用的结果啊。
添加消息后的消息列表结构如下:
[
{
'role': 'system',
'content': '你是一个智能助手'
},
{
'role': 'user',
'content': '广州今天天气怎么样?'
},
{
'role': 'assistant',
'reasoning_content': '用户想知道广州今天的天气。我需要调用get_weather工具来获取广州的天气信息。',
'content': '好的,我来查一下广州今天的天气情况。',
'tool_calls': [
{
'id': 'call_00_2EYhsH2JSNemc3V1JJ0T6811',
'type': 'function',
'function': {
'name': 'get_weather',
'arguments': '{
"city": "广州"
}'
}
}
]
},
{
'role': 'tool',
'tool_call_id': 'call_00_2EYhsH2JSNemc3V1JJ0T6811',
'content': '上午晴朗无云,下午2: 00-4: 00有暴雨'
}
]
最后看一下这段代码的输出效果:
思考过程: 用户想知道广州今天的天气。我需要调用get_weather工具来获取广州的天气信息。
回答: 好的,我来查一下广州今天的天气情况。
tool调用: {'id': 'call_00_2EYhsH2JSNemc3V1JJ0T6811', 'name': 'get_weather', 'arguments': '{"city": "广州"}'}
tool返回结果: 上午晴朗无云,下午2:00-4:00有暴雨
思考过程: 工具返回了广州今天的天气信息:上午晴朗无云,下午2:00-4:00有暴雨。我可以直接告诉用户。
回答: 广州今天的天气情况如下:
🌤️ **上午**:晴朗无云,天气不错。
⛈️ **下午 2:00 - 4:00**:会有暴雨,请注意防范。
建议您如果今天有出门计划,**上午出行比较适宜**,下午时段最好带好雨具,避免在暴雨时段外出哦!
然后呢,这个标题到此结束了吗?很遗憾,并没有,新的斗争又开始了。
get_weather 是一个很简单的函数,如果我的工具的输入参数非常复杂怎么办,不是一个 city 参数怎么办?
其实这个早有爆料,parameters 里面的 type 就是一个对象。

如果输入参数非常复杂,是基本数据类型套对象,对象里面再套对象。不慌,我们的 parameters 也是可以套娃的。
parameters 必须遵循 JSON Schema 的规则,对 json 结构进行约束,具体看这里:一文看懂 JSON Schema:让“杂乱 JSON”变得可控_jsonschema-CSDN博客。
但是,你输入参数可以介绍复杂的参数,那输出参数呢?
可以在 description 里面介绍输出参数的结构,越详细越好。
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个数据库智能助手,保存了用户管理系统的所有结构化数据"},
{"role": "user", "content": "我需要获取用户ID=1的这个用户的信息"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_user_info",
"description": """
根据用户ID查询用户详细信息,返回的数据结构如下:{
"id": 用户id,
"username": 用户名,
"password": 用户密码,
"gender": 用户的性别,
"plan": 用户购买的套餐,plan是一个字典,里面的结构是:{
"name": 套餐名称,
"price": 套餐价格,
"type": 套餐时长,有0/1/2三种取值,分别表示包月/包季/包年
}
}
""",
"parameters": {
"type": "object",
"properties": {
"user": {
"type": "object",
"description": "用户标识信息",
"properties": {
"id": {"type": "string", "description": "用户ID"}
},
"required": ["id"]
}
},
"required": ["user"]
}
}
}
],
"tool_choice": "auto",
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
4.一次调用多个 tool
既然我们知道了怎么调用一个 tool,那么进阶课程来了,怎么调用多个 tool 呢?
还是以获取天气为例,如果我们要获取天津和广州的天气呢?
不急,同样先看接口返回的数据,要具体问题具体分析。
{'role': 'assistant', 'content': None, 'reasoning_content': ''}
{'content': None, 'reasoning_content': '用户'}
{'content': None, 'reasoning_content': '想'}
{'content': None, 'reasoning_content': '查询'}
{'content': None, 'reasoning_content': '广州'}
{'content': None, 'reasoning_content': '和'}
{'content': None, 'reasoning_content': '天津'}
{'content': None, 'reasoning_content': '的'}
{'content': None, 'reasoning_content': '天气'}
{'content': None, 'reasoning_content': '。'}
{'content': None, 'reasoning_content': '我需要'}
{'content': None, 'reasoning_content': '调用'}
{'content': None, 'reasoning_content': ' get'}
{'content': None, 'reasoning_content': '_'}
{'content': None, 'reasoning_content': 'weather'}
{'content': None, 'reasoning_content': ' '}
{'content': None, 'reasoning_content': '工具'}
{'content': None, 'reasoning_content': '来'}
{'content': None, 'reasoning_content': '获取'}
{'content': None, 'reasoning_content': '这两个'}
{'content': None, 'reasoning_content': '城市的'}
{'content': None, 'reasoning_content': '天气'}
{'content': None, 'reasoning_content': '信息'}
{'content': None, 'reasoning_content': '。'}
{'content': None, 'reasoning_content': '这两个'}
{'content': None, 'reasoning_content': '查询'}
{'content': None, 'reasoning_content': '是'}
{'content': None, 'reasoning_content': '独立的'}
{'content': None, 'reasoning_content': ','}
{'content': None, 'reasoning_content': '可以'}
{'content': None, 'reasoning_content': '同时'}
{'content': None, 'reasoning_content': '调用'}
{'content': None, 'reasoning_content': '。'}
{'content': '好的', 'reasoning_content': None}
{'content': ',', 'reasoning_content': None}
{'content': '我来', 'reasoning_content': None}
{'content': '帮你', 'reasoning_content': None}
{'content': '查', 'reasoning_content': None}
{'content': '一下', 'reasoning_content': None}
{'content': '广州', 'reasoning_content': None}
{'content': '和', 'reasoning_content': None}
{'content': '天津', 'reasoning_content': None}
{'content': '今天的', 'reasoning_content': None}
{'content': '天气', 'reasoning_content': None}
{'content': '情况', 'reasoning_content': None}
{'content': '。', 'reasoning_content': None}
{'tool_calls': [{'index': 0, 'id': 'call_00_7dxplVqBxECt5301ECAu9370', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': ''}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '{'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': 'city'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': ': '}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '广州'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 0, 'function': {'arguments': '}'}}]}
{'tool_calls': [{'index': 1, 'id': 'call_01_XduNlxsu0mpEzT0iqgty8205', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': ''}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '{'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': 'city'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': ': '}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '天津'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '"'}}]}
{'tool_calls': [{'index': 1, 'function': {'arguments': '}'}}]}
{'content': '', 'reasoning_content': None}
可以看到,在 tool_calls 中出现了两个对象,用 index 进行区分,index 越小越先执行。
而且虽然调用的是同一个函数,但是两个对象的 id 是不同的。
那知道怎么写代码了吗?之前因为我们非常确定只会调用一次工具,所以 tool_call 是一个对象,现在 tool_call 可不能是一个对象了,应该换成数组结构,可以保存多个对象。
而且,每个对象已经用 index 给我们排好序了,第一个对象放在下标 = 0 的位置,第二个对象就放在下标 = 1 的位置。
import requests
import json
def get_weather(city):
if city == "广州":
return "上午晴朗无云,下午2:00-4:00有暴雨"
else:
return "高温红色预警,且最近三天天气以高温晴热为主,气温上升至35℃以上"
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-aef75cba11c94fb2be77e046f8b41739"
}
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "今天广州和天津的天气怎么样?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取某个城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto",
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
while True:
reasoning = ""
content = ""
tool_call = []
with requests.post(url, headers=headers, json=payload) as r:
for line in r.iter_lines():
decode_str = line.decode("utf-8")
if decode_str:
if decode_str.startswith('data: '):
json_str = decode_str[6:]
if json_str.strip() == '[DONE]':
break
else:
delta = json.loads(json_str)["choices"][0]["delta"]
if "reasoning_content" in delta and delta["reasoning_content"]:
reasoning += delta["reasoning_content"]
if "content" in delta and delta["content"]:
content += delta["content"]
if "tool_calls" in delta:
t = delta["tool_calls"][0]
dict_obj = None
if "index" in t:
if len(tool_call) > t["index"]:
dict_obj = tool_call[t["index"]]
else:
dict_obj = {}
tool_call.append(dict_obj)
if "id" in t:
dict_obj["id"] = t["id"]
if "function" in t:
fn = t["function"]
if "name" in fn:
if "name" in dict_obj:
dict_obj["name"] += fn["name"]
else:
dict_obj["name"] = fn["name"]
if "arguments" in fn:
if "arguments" in dict_obj:
dict_obj["arguments"] += fn["arguments"]
else:
dict_obj["arguments"] = fn["arguments"]
if len(tool_call) == 0:
print("思考过程:", reasoning)
print("回答:", content)
break
else:
result = []
tool_message = []
payload["messages"].append(
{
"role": "assistant",
"reasoning_content": reasoning,
"content": content,
"tool_calls": [
],
})
for e in tool_call:
function_name = e["name"]
if function_name == "get_weather":
input_json = json.loads(e["arguments"])
r = get_weather(input_json["city"])
result.append(r)
lastMessage = payload["messages"][-1]
calls = lastMessage["tool_calls"]
calls.append({
"id": e["id"],
"type": "function",
"function": {
"name": e["name"],
"arguments": e["arguments"],
},
})
tool_message.append({"role": "tool", "tool_call_id": e["id"], "content": r})
for e in tool_message:
payload["messages"].append(e)
print("思考过程:", reasoning)
print("tool调用:", tool_call)
print("tool返回结果:", result)
[
{
'role': 'system',
'content': '你是一个智能助手'
},
{
'role': 'user',
'content': '今天广州和天津的天气怎么样?'
},
{
'role': 'assistant',
'reasoning_content': '用户想知道今天广州和天津的天气。我需要调用get_weather工具来获取这两个城市的天气信息。这两个调用是独立的,可以同时进行。',
'content': '我来帮你查一下广州和天津今天的天气情况!',
'tool_calls': [
{
'id': 'call_00_dKc2lWLzUUbncWEW5AOP1423',
'type': 'function',
'function': {
'name': 'get_weather',
'arguments': '{
"city": "广州"
}'
}
},
{
'id': 'call_01_DVTvRc44bAwrEDgG4vEn1389',
'type': 'function',
'function': {
'name': 'get_weather',
'arguments': '{
"city": "天津"
}'
}
}
]
},
{
'role': 'tool',
'tool_call_id': 'call_00_dKc2lWLzUUbncWEW5AOP1423',
'content': '上午晴朗无云,下午2: 00-4: 00有暴雨'
},
{
'role': 'tool',
'tool_call_id': 'call_01_DVTvRc44bAwrEDgG4vEn1389',
'content': '高温红色预警,且最近三天天气以高温晴热为主,气温上升至35℃以上'
}
]
5.tool 边界
在前面,因为我们非常确定,AI 模型是不能联网查询的,所以必须要调 get_weather 函数。
但是,对于 AI 模型可以正常回答的问题,它还会调用工具吗?
我们可以看到,AI 模型在回答 "广州有哪些美食" 这个问题是不需要调用工具的。

现在我们准备一个 tools 函数,再来试试看。
def get_food_recommendations(city):
if city == "广州":
food = []
food.append({"food_name": "虾饺皇",
"description": "作为广式早茶“四大天王”之首,虾饺皇以澄粉作皮,薄透晶莹且折有褶子。内包整只鲜虾与笋丁,一口咬下汁水四溢,口感鲜甜弹牙。"})
food.append({"food_name": "干蒸烧卖",
"description": "早茶中的另一款灵魂点心,以猪肉和虾仁为馅,顶部常点缀蟹籽。外皮干爽不粘牙,肉馅紧实多汁,口感鲜香脆爽。"})
food.append({"food_name": "蜜汁叉烧包",
"description": "传统老广的童年味道,松软的面皮包裹着肥瘦相间的蜜汁叉烧。顶部自然开花,咸甜交织,是早茶桌上不可或缺的经典主食。"})
food.append({"food_name": "白切鸡",
"description": "“无鸡不成宴”的极致体现。选用清远麻鸡经“三浸三提”浸熟冰镇,皮爽肉滑、骨带微红。搭配灵魂姜葱蓉,最大程度保留了鸡肉的本真鲜甜。"})
food.append({"food_name": "深井烧鹅",
"description": "广式烧腊的巅峰之作。黑棕鹅用荔枝木明火吊烤,表皮呈现诱人的琥珀色且酥脆如琉璃。肉质鲜嫩多汁,蘸上酸梅酱解腻增香。"})
food.append({"food_name": "干炒牛河",
"description": "这道菜极其考验厨师功底,被视为粤菜厨师的“试金石”。大火猛炒出的河粉根根分明、油润干爽,牛肉滑嫩,韭黄提香,镬气十足。"})
return json.dumps(food, ensure_ascii=False)
url = "https://api.deepseek.com/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-aef75cba11c94fb2be77e046f8b41739"
}
payload = {
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是一个智能助手"},
{"role": "user", "content": "广州有什么美食?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_food_recommendations",
"description": """
获取某个城市的特色美食,返回的是一个数组,结构是[
{"food_name": "xxx","description": "xxx"},
{"food_name": "xxx","description": "xxx"},
{...}
]
""",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto",
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": True
}
DeepSeek-v4-flash 在面对这个不需要额外工具就可以回答的问题时,还是调用了工具。
思考过程: 用户想知道广州有什么美食,我可以使用get_food_recommendations工具来获取广州的特色美食推荐。
tool调用: [{'id': 'call_00_YgxV6Fqq35r84RzKAdET8995', 'name': 'get_food_recommendations', 'arguments': '{"city": "广州"}'}]
tool返回结果: ['[{"food_name": "虾饺皇", "description": "作为广式早茶“四大天王”之首,虾饺皇以澄粉作皮,薄透晶莹且折有褶子。内包整只鲜虾与笋丁,一口咬下汁水四溢,口感鲜甜弹牙。"}, {"food_name": "干蒸烧卖", "description": "早茶中的另一款灵魂点心,以猪肉和虾仁为馅,顶部常点缀蟹籽。外皮干爽不粘牙,肉馅紧实多汁,口感鲜香脆爽。"}, {"food_name": "蜜汁叉烧包", "description": "传统老广的童年味道,松软的面皮包裹着肥瘦相间的蜜汁叉烧。顶部自然开花,咸甜交织,是早茶桌上不可或缺的经典主食。"}, {"food_name": "白切鸡", "description": "“无鸡不成宴”的极致体现。选用清远麻鸡经“三浸三提”浸熟冰镇,皮爽肉滑、骨带微红。搭配灵魂姜葱蓉,最大程度保留了鸡肉的本真鲜甜。"}, {"food_name": "深井烧鹅", "description": "广式烧腊的巅峰之作。黑棕鹅用荔枝木明火吊烤,表皮呈现诱人的琥珀色且酥脆如琉璃。肉质鲜嫩多汁,蘸上酸梅酱解腻增香。"}, {"food_name": "干炒牛河", "description": "这道菜极其考验厨师功底,被视为粤菜厨师的“试金石”。大火猛炒出的河粉根根分明、油润干爽,牛肉滑嫩,韭黄提香,镬气十足。"}]']
思考过程: 广州的美食非常丰富,我来整理一下这些信息给用户。
回答: 广州作为美食之都,好吃的实在太多啦!让我为你介绍几道经典的广州特色美食 🥟🍗
---
### 🥟 早茶点心系列
**1. 虾饺皇** — 广式早茶"四大天王"之首
澄粉做的外皮薄透晶莹,里面包着整只鲜虾和笋丁,一口咬下去鲜甜弹牙、汁水四溢。
**2. 干蒸烧卖** — 早茶灵魂点心
猪肉虾仁馅紧实多汁,顶部点缀蟹籽,口感鲜香脆爽。
**3. 蜜汁叉烧包** — 老广的童年味道
松软的面皮包裹肥瘦相间的蜜汁叉烧,顶部自然开花,咸甜交织。
---
### 🍗 经典粤菜
**4. 白切鸡** — "无鸡不成宴"的极致体现
选用清远麻鸡,经"三浸三提"浸熟冰镇,皮爽肉滑、骨带微红,搭配灵魂姜葱蓉,完美保留鸡肉本味。
**5. 深井烧鹅** — 广式烧腊巅峰之作
黑棕鹅用荔枝木明火吊烤,表皮酥脆如琉璃,肉质鲜嫩多汁,蘸上酸梅酱解腻增香。
---
### 🍜 街头美味
**6. 干炒牛河** — 粤菜厨师的"试金石"
大火猛炒,河粉根根分明、油润干爽,牛肉滑嫩,韭黄提香,**镬气**十足!
---
广州的美食文化博大精深,光是早茶就能吃上大半天!如果你计划去广州游玩,这些可都是必尝的地道美味哦~😋 还有其他想了解的吗?
这里再勾一下前面提到的 tool_choice 这个属性。

我这里用的是 auto,感觉 AI 模型还是比较倾向于使用 tools 的。
为了控制 tool 调用的边界,我觉得比较好的实践是:
- tools 的描述越详细,AI 模型的判断越准确。
- 我们应该给 AI 模型真正需要用到的地方提供 tools,很多知识 AI 模型已经训练过,有答案了,这个时候如果还调用 tools,就会影响输出结果。比如广州美食的问题,如果调用工具,就只会返回固定的数据。
更多推荐




所有评论(0)