万字长文详解Qwen-agent的Function Calling(函数调用)
前言
在之前的文章中,我们完成了Qwen-Agent的demo运用,这篇文章中,咱们一起来实现自定义Qwen-agent的Function Calling~
最后运行效果:
Qwen 函数调用的核心组件介绍
大型语言模型理解并遵循此协议有多种方式。关键在于提示工程 (Prompt Engineering) 或模型内化的模板。Qwen预先训练了多种支持函数调用的模板,以便用户可以直接利用这一过程。
Qwen 的函数调用框架包含以下关键组件:
- 函数定义:以 JSON 模式描述可用函数,包括函数名称、描述、参数和预期输出。模型通过这些模式了解可用工具及其使用方法。
- 聊天接口:Qwen 的聊天模型处理用户查询,决定生成文本响应还是函数调用。
- 工具集成:函数作为外部工具实现,系统根据模型输出执行函数,并将结果返回给模型以生成最终响应。
使用函数调用进行推理
由于函数调用本质上是通过提示工程实现的,您可以手动构建Qwen模型的输入。但是,支持函数调用的框架可以帮助您完成所有繁重的工作(在实战部分,我们将介绍通过专用的函数调用模板——Qwen-Agent来进行推理)。
案例
我们同样通过一个示例来展示推理的使用方法。
场景:假设我们要询问模型某个地点的温度。通常,模型会回答无法提供实时信息。但我们有两个工具,可以分别获取城市的当前温度和指定日期的温度,我们希望模型能够利用这些工具。
为了这个示例案例,使用以下代码:
import json
def get_current_temperature(location: str, unit: str = "celsius"):
"""获取指定地点的当前温度。
参数:
location: 要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。
unit: 返回温度的单位,默认为“celsius”。(可选值:["celsius", "fahrenheit"])
返回:
包含温度、地点和单位的字典
"""
return {
"temperature": 26.1,
"location": location,
"unit": unit,
}
def get_temperature_date(location: str, date: str, unit: str = "celsius"):
"""获取指定地点和日期的温度。
参数:
location: 要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。
date: 要获取温度的日期,格式为“年-月-日”。
unit: 返回温度的单位,默认为“celsius”。(可选值:["celsius", "fahrenheit"])
返回:
包含温度、地点、日期和单位的字典
"""
return {
"temperature": 25.9,
"location": location,
"date": date,
"unit": unit,
}
def get_function_by_name(name):
if name == "get_current_temperature":
return get_current_temperature
if name == "get_temperature_date":
return get_temperature_date
TOOLS = [
{
"type": "function",
"function": {
"name": "get_current_temperature",
"description": "获取指定地点的当前温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "返回温度的单位,默认为“celsius”。",
"default": "celsius"
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_temperature_date",
"description": "获取指定地点和日期的温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。",
},
"date": {
"type": "string",
"description": "要获取温度的日期,格式为“年-月-日”。",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "返回温度的单位,默认为“celsius”。",
"default": "celsius"
},
},
"required": ["location", "date"],
},
},
},
]
MESSAGES = [
{
"role": "system", "content": "你是 Qwen,由阿里巴巴云创建。你是一个乐于助人的助手。\n\n当前日期:2025-05-06"},
{
"role": "user", "content": "上海现在的温度是多少?明天呢?"},
]
工具应使用JSON Schema进行描述,消息应包含尽可能多的有效信息。您可以在下面找到工具和消息的解释:
示例工具
工具应使用以下JSON进行描述:
TOOLS = [
{
"type": "function",
"function": {
"name": "get_current_temperature",
"description": "获取指定地点的当前温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "返回温度的单位,默认为“celsius”。",
"default": "celsius"
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_temperature_date",
"description": "获取指定地点和日期的温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "要获取温度的地点,格式为“城市, 省, 国家”(例如“上海, 上海, 中国”)。",
},
"date": {
"type": "string",
"description": "要获取温度的日期,格式为“年-月-日”。",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "返回温度的单位,默认为“celsius”。",
"default": "celsius"
},
},
"required": ["location", "date"],
},
},
},
]
对于每个工具,它是一个具有两个字段的JSON object:
-
type:string,用于指定工具类型,目前仅"function"有效
-
function:object,详细说明了如何使用该函数
对于每个function,它是一个具有三个字段的JSON object:
-
name:string 表示函数名称
-
description:string 描述函数用途
-
parameters:JSON Schema,用于指定函数接受的参数。请参阅链接文档以了解如何构建JSON Schema。值得注意的字段包括type、required和enum。
大多数框架使用“工具”格式,有些可能使用“函数”格式。根据命名,应该很明显应该使用哪一个。
示例消息
我们的查询是“上海现在的温度是多少?明天呢?”。由于模型不知道当前日期,更不用说明天了,我们应该在输入中提供日期。在这里,我们决定在默认系统消息“你是 Qwen,由阿里巴巴云创建。你是一个乐于助人的助手。”之后的系统消息中提供该信息。您可以在应用程序代码中将日期附加到用户消息。
MESSAGES = [
{
"role": "system", "content": "你是 Qwen,由阿里巴巴云创建。你是一个乐于助人的助手。\n\n当前日期:2025-05-06"},
{
"role": "user", "content": "上海现在的温度是多少?明天呢?"},
]
Qwen-Agent
Qwen-Agent 实际上是一个用于开发AI应用的Python智能体框架。尽管其设计用例比高效推理更高级,但它确实包含了Qwen2.5函数调用的规范实现。基于OpenAI兼容API,它可以通过模板为Qwen2.5提供了对用户透明的的函数调用能力。
值得注意的是,由于应用框架可以在幕后完成大量工作,目前Qwen2.5官方的函数调用实现非常灵活且超出了简单的模板化,这使得它难以适应那些使用能力较弱的模板引擎的其他框架。
在开始之前,让我们确保已安装了最新的库:
git clone --branch v0.0.21 https://github.com/QwenLM/Qwen-Agent.git
cd Qwen-Agent
pip install -e ./"[gui,rag,code_interpreter,mcp]"
# 本指南使用版本 v0.0.21。
准备工作
Qwen-Agent可以封装一个不支持函数调用的OpenAI兼容API。您可以使用大多数推理框架来提供此类API,或者从DashScope或Together等云提供商处获取一个。
假设在http://localhost:23333/v1处有一个OpenAI兼容API,Qwen-Agent提供了一个快捷函数get_chat_model,用于获取具有函数调用支持的模型推理类:
from qwen_agent.llm import get_chat_model
llm = get_chat_model({
'model': '/share/new_models/qwen3/Qwen3-8B/',
"model_server": "http://localhost:23333/v1",
"api_key": "EMPTY",
})
在上述代码中,model_server是其他OpenAI兼容API客户端常用的api_base。建议您提供api_key(但不要以明文形式出现在代码中),即使API服务器不检查它,在这种情况下,您可以将其设置为任何值。
对于模型输入,应使用系统、用户和助手历史记录的通用消息结构:
messages = MESSAGES[:]
# [
# {"role": "system", "content": "你是 Qwen,由阿里巴巴云创建。你是一个乐于助人的助手。\n\n当前日期:2025-05-06"},
# {"role": "user", "content": "上海现在的温度是多少?明天呢?"}
# ]
我们在系统消息中添加当前日期,以便使用户消息中的”明天”有明确的参照点。如果需要,也可以将其添加到用户消息中。
目前,Qwen-Agent使用“函数”而非“工具”。这需要对我们工具描述进行一些小的更改,即提取函数字段:
functions = [tool["function"] for tool in TOOLS]
工具调用和工具结果
为了与模型交互,应使用chat方法:
for responses in llm.chat(
messages=messages,
functions=functions,
extra_generate_cfg=dict(parallel_function_calls=True),
):
pass
messages.extend(responses)
# 打印最终响应
print(json.dumps(messages, ensure_ascii=False, indent=2))
#这行代码的作用是将 messages 列表转换为 JSON 格式的字符串,在转换过程中允许非 ASCII 字符直接输出,并且为生成的 JSON 字符串添加缩进,最后将转换后的 JSON 字符串打印到控制台。
在上述代码中,chat方法接收messages、functions以及一个extra_generate_cfg参数。你可以在extra_generate_cfg中放入诸如temperature和top_p等采样参数。这里,我们添加了Qwen-Agent提供的特殊控制parallel_function_calls。顾名思义,它将启用并行函数调用,这意味着模型可能为单次请求生成多个函数调用,按照其判断进行。
chat方法返回一个列表的生成器,每个列表可能包含多条消息。因为我们启用了parallel_function_calls,我们应该在响应中得到:
即:
[
{
"role": "system",
"content": "你是 Qwen,由阿里巴巴云创建。你是一个乐于助人的助手。\n\n当前日期:2025-05-06"
},
{
"role": "user",
"content": "上海现在的温度是多少?明天呢?"
},
{
"role": "assistant",
"content": "<think>\n好的,用户问的是上海现在的温度以及明天的温度。首先,我需要确定用户需要两个不同的温度数据:当前温度和明天同一时间的温度。当前温度可以用get_current_temperature函数来获取,而明天的温度需要使用get_temperature_date函数,并且日期参数要设置为明天的日期。\n\n首先,调用get_current_temperature函数,参数是location为“上海, 上海, 中国”,单位默认是摄氏度。接下来,确定明天的日期。今天是2025年5月6日,所以明天是5月7日。然后调用get_temperature_date函数,参数是location同样为“上海, 上海, 中国”,date设置为“2025-05-07”,单位同样保持默认的摄氏度。需要确保日期格式正确,符合YYYY-MM-DD的模式。这样就能获取到用户所需的两个温度数据了。\n</think>\n\n",
"reasoning_content": ""
},
{
"role": "assistant",
"content": "",
"reasoning_content": "",
"function_call": {
"name": "get_current_temperature",
"arguments": "{
\"location\": \"上海, 上海, 中国\"}"
}
},
{
"role": "assistant",
"content": "",
"reasoning_content"