Qwen Function Calling 详细解析


项目地址:https://github.com/EvannZhongg/Blog-Learning.git


1. Function Calling 简介

Function Calling 允许 LLM(大语言模型)在回答问题时调用外部工具,如 Python 函数、API 或数据库查询。

核心流程:

  1. 用户输入问题
  2. AI 判断是否需要调用工具
  3. 如果需要,AI 返回 tool_calls,请求调用特定函数
  4. 开发者执行对应的 Python 函数,并返回执行结果
  5. AI 读取工具返回值,继续推理并生成最终回答

2. 代码拆解:Function Calling 功能实现

本文定义了一个read_markdown函数,用于通过提问让大模型自主调用函数阅读markdown文本内容,回答问题。

(1)初始化 Qwen 客户端

1
2
3
4
client = OpenAI(
api_key="your_api_key",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

作用:

  • 连接 Qwen API 进行请求。
  • api_key 必须正确,否则请求会失败。

(2)定义工具(Function Calling)

1
2
3
4
5
6
7
8
9
10
11
12
13
tools = [
{
"type": "function",
"function": {
"name": "read_markdown",
"description": "读取 'documents' 文件夹中的 Markdown 文档。",
"parameters": {
"type": "object",
"properties": {}, # 这个工具不需要参数
},
}
}
]

作用:

  • 告诉 Qwen 可调用的工具。
  • name 必须与 Python 函数名一致,否则无法正确调用。
  • description 用于让 AI 知道工具的用途。
  • 在 Qwen 的 Function Calling 中,工具的parameterstype 必须是 object
  • parameters 为空 {},表示此工具不需要参数。
  • parameters 如果不为空,表示此工具需要传递参数
    • properties 定义了每个参数的名称、类型和描述。
    • required 指定了哪些参数是必填项。

如果代码中涉及到了多个工具的调用,可按照以下的格式进行多个工具的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
tools = [
{
"type": "function",
"function": {
"name": "read_markdown",
"description": "读取 'documents' 文件夹中的 Markdown 文档。",
"parameters": {
"type": "object",
"properties": {}, # 这个工具不需要参数
},
}
},
{
"type": "function",
"function": {
"name": "fetch_weather",
"description": "获取指定城市的天气信息。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "要查询天气的城市名称"
}
},
"required": ["city"]
},
}
}
]

示例解释:

  • read_markdown 不需要参数,所以 properties 为空。
  • fetch_weather 需要参数,它的 parameters 里包含 city 这个字符串参数。
  • AI 在调用 fetch_weather 时,会传递 JSON 结构的 arguments,如:
    1
    2
    3
    {
    "city": "Shanghai"
    }

(3)读取 Markdown 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def read_markdown():
doc_path = "documents"
md_file = None

for file in os.listdir(doc_path):
if file.endswith(".md"):
md_file = os.path.join(doc_path, file)
break

if not md_file:
return "No markdown file found in the 'documents' folder."

with open(md_file, "r", encoding="utf-8") as f:
content = f.read()

return content[:5000]

作用:

  • 读取 documents/ 目录下后缀为 .md 文件。
  • 限制 5000 字符,防止超出 Token 限制。
  • markdown文本的前5000个字符传递给AI。

(4)获取天气信息(示例工具)

1
2
3
4
5
def fetch_weather(city):
"""
获取指定城市的天气信息。
"""
return f"当前 {city} 的天气:晴,25°C"

作用:

  • 模拟获取天气信息,参数 city 必须传递
  • Qwen 在调用 fetch_weather 时,会自动提供 city 的值。

(5)主逻辑:工具调用的完整流程

1
2
3
def main():
user_question = input("请输入问题:").strip()
messages = [{"role": "user", "content": user_question}]

作用:

  • 让用户输入问题,并存入 messages 作为对话历史。

(6)第一次请求:看看 AI 是否调用工具

1
2
3
4
5
completion = client.chat.completions.create(
model="qwen-plus",
messages=messages,
tools=tools
)

作用:

  • 调用 Qwen,让其决定是否需要调用工具。

(7)检查 Qwen 是否调用了工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if tool_calls:
tool_results = []
for tool_call in tool_calls:
tool_name = tool_call["function"]["name"]
tool_id = tool_call["id"]
tool_args = tool_call["function"]["arguments"]

if tool_name == "read_markdown":
result_content = read_markdown()
elif tool_name == "fetch_weather":
city = tool_args.get("city", "未知城市")
result_content = fetch_weather(city)

tool_results.append({
"role": "tool",
"tool_call_id": tool_id,
"content": result_content
})

作用:

  • 解析 Qwen 返回的 tool_calls
  • 调用不同的工具,如 read_markdown()fetch_weather(city) ,并存入 tool 消息。
  • 如果工具需要参数,就从 tool_args 里提取。
  • tool_calls 为空,说明 AI 不需要调用工具

(8)让 Qwen 继续推理

1
2
3
4
5
6
7
8
9
10
11
12
messages.append({
"role": "assistant",
"content": "",
"tool_calls": tool_calls
})
messages.extend(tool_results)

completion2 = client.chat.completions.create(
model="qwen-plus",
messages=messages,
tools=tools
)

作用:

  • 让 Qwen 读取工具返回值,并继续回答

效果测试:

function_calling


3. 以下是我在测试的时候一些工具调用失败的原因

  1. name 和 Python 函数不匹配,导致 AI 无法正确调用工具。
  2. role="tool" 消息未紧跟 assistant,导致 AI 解析错误。
  • 解决方法是在调用完工具之后,向 messages 中手动插入一条 role=”assistant” 的空消息,附带 tool_calls 字段,然后紧接着添加 role=”tool” 的返回结果,这样就保证了正确的调用链路。
    1
    2
    3
    4
    5
    6
    messages.append({
    "role": "assistant",
    "content": "", # 一定为空
    "tool_calls": tool_calls # 将 AI 最初返回的 tool_calls 原样放回
    })
    messages.extend(tool_results) # 马上接上 tool 返回值
  • 因为 Qwen 模型的调用链规则如下:
    1
    2
    3
    4
    用户消息(role="user")
    → AI 返回 tool_calls(role="assistant",tool_calls)
    → 工具执行,返回结果(role="tool",tool_call_id)
    → AI 接收到结果,继续推理
  • 如果 role=”tool” 的消息不紧跟在带有 tool_calls 的 assistant 消息后面,模型就无法对上“这个工具调用返回了这个值”,于是会报错或输出异常。
  1. parameters 设置错误,导致 AI 传递了错误的参数。
  2. messages 结构错误,导致 AI 不能正确读取历史对话。

该项目代码参考 Qwen官方Function Calling