京东自营 + 国补 iPhone 历史最低价          国家补贴 享8折

仅需2步,解锁 QwQ-32B 的 Function Call 超能力

前不久,我们发布了推理模型通义千问QwQ-32B,在 LiveBench 榜单上取得了开源模型的最佳成绩。

在针对模型的 Agent 能力的 BFCL评测中,千问 QwQ-32B 模型在工具调用准确率、复杂参数解析等核心指标上均有不凡表现,展现出领先的函数调用与多步骤推理能力。


今天,我们将分享如何简单方便地基于 QwQ-32B 进行 Function Call 的调用,通过标准化代码案例,系统讲解其在工具调用场景中的技术实现与应用实践。


调用QwQ-32B的Function Call,我们以以下两种方法为例:

(1)百炼API

(2)Qwen-Agent开源框架


准备工作:

(1)定义自己的工具 Schema

(2)实现工具的执行函数

TOOLS = [{
    "name": "train_tickets",
    "description": "Obtain train tickets and other information based on departure, arrival, and time.",
    "parameters": {
        "type": "object",
        "properties": {
            "start": {
                "type": "string",
                "description": "The departure city."
            },
            "end": {
                "type": "string",
                "description": "The arrival city."
            },
            "date": {
                "type": "string",
                "description": "The date, such as 2024-02-21. If not clear, do not fill in."
            }
        },
        "required": [
            "start",
            "end",
            "date"
        ]
    }
},
{
    "name": "get_weather",
    "description": "Weather inquiry.",
    "parameters": {
        "type": "object",
        "properties": {
            "area": {
                "type": "string",
                "description": "Regional name, such as 丽江"
            }
        },
        "required": [
            "area"
        ]
    }
}]

import json

def get_weather(area: str):
    # 作为示例,这里定义一个假的工具执行函数
    res = {
        "area": "上海",
        "weather": {
            "areaid": "101020100",
            "temperature": "12",
            "weather": "晴",
            "wind_direction": "西风",
            "wind_power": "0-3级 微风  <5.4m/s",
            "weather_code": "00"
        },
    }
    return json.dumps(res, ensure_ascii=False)


def train_tickets(start: str, end: str, date: str):
    # 作为示例,这里定义一个假的工具执行函数
    if start == '上海' and end == '杭州':
        res = {
                "costtime": "1小时19分",
                "trainno": "C3629",
                "arrivaltime": "17:34",
                "endstation": "杭州西",
                "terminalstationcode": "YWH",
                "departstation": "上海虹桥",
                "station": "上海虹桥",
                "departuretime": "16:15",
                "departstationcode": "AOH",
            }
    elif start == '杭州' and end == '上海':
        res = {
            "costtime": "0小时48分",
            "trainno": "G7336",
            "arrivaltime": "8:34",
            "endstation": "上海南",
            "terminalstationcode": "SNH",
            "departstation": "苍南",
            "station": "杭州东",
            "departuretime": "7:46",
            "departstationcode": "CEH",
        }
    else:
        res = {"train_list": "No available train"}
    return json.dumps(res, ensure_ascii=False)


TOOL_EXEC_MAP = {
    'train_tickets': train_tickets,
    'get_weather': get_weather
}

1. 百炼API:

运行以下代码,完成一次含有工具调用的请求,主要包含三个步骤:

(1)发起模型请求,输入用户问题和工具

(2)读取返回内容,如果返回内容包含工具调用,则执行工具,获得工具结果

(3)再次发起模型请求,输入工具结果。循环(1)-->(3)。

import os
from openai import OpenAI

# 初始化OpenAI客户端,配置阿里云DashScope服务
client = OpenAI(
    # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx",
    api_key=os.getenv("DASHSCOPE_API_KEY"),  # 从环境变量读取API密钥
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

# 输入用户请求
messages = [{"role": "user", "content": "今天是2025年3月17号,我马上要去上海出差,当天往返,请帮我查询今天从杭州到上海往返的火车票,此外,再帮我查询一下上海今天的天气。"}]

while True:
    completion = client.chat.completions.create(
        model="qwq-32b",
        messages=messages,
        tools=[{"type": "function", "function": fn} for fn in TOOLS],  # 百炼API要求输入的工具格式
        parallel_tool_calls=True,
        stream=True,
    )

     # 打印模型流式回复
    reasoning_content = ""
    answer_content = ""
    tool_calls = []
    is_answering = False
    print('\n[思考内容]: \n')
    for chunk in completion:
        delta = chunk.choices[0].delta
        # AI的思考过程
        if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
            reasoning_content += delta.reasoning_content
            print(delta.reasoning_content, end="", flush=True)  # 实时输出思考过程

        # 最终回复内容
        if delta.content is not None:
            if not is_answering:
                is_answering = True
                print('\n[回复内容]: \n')
            answer_content += delta.content
            print(delta.content, end="", flush=True)  # 流式输出回复内容

        # 工具调用信息(支持并行工具调用)
        if delta.tool_calls is not None:
            for tool_call in delta.tool_calls:
                index = tool_call.index
                # 动态扩展工具信息存储列表
                if len(tool_calls) <= index:
                    tool_calls.append({'type': 'function', 'function': {'name': tool_call.function.name, 'arguments': tool_call.function.arguments}})
                else:
                    # 收集函数参数(JSON字符串格式,需要后续解析)
                    if tool_call.function and tool_call.function.arguments:
                        tool_calls[index]['function']['arguments'] += tool_call.function.arguments

    # 判断是否发起工具调用,如果有,则执行工具
    if tool_calls:
        print('\n[工具调用]: \n', tool_calls)
        response = [{'role': 'assistant', 'content': answer_content, 'reasoning_content': reasoning_content, 'tool_calls': tool_calls}]

        tool_response = []
        for tool_call in tool_calls:
            tool_name = tool_call['function']['name']
            tool_args = json.loads(tool_call['function']['arguments'])
            tool_exec_res = TOOL_EXEC_MAP[tool_name](**tool_args)
            tool_response.append({'role': 'tool', 'content': tool_exec_res, 'name': tool_name})
        print('\n[工具结果]: \n', tool_response)

        # 拼接工具结果,和LLM进行下一轮对话
        messages.extend(response)
        messages.extend(tool_response)
    else:
        # 无工具执行,对话结束
        response = [{'role': 'assistant', 'content': answer_content, 'reasoning_content': reasoning_content}]
        messages.extend(response)
        break

以上代码输出为:

[
    {"role": "user", "content": "今天是2025年3月17号,我马上要去上海出差,当天往返,请帮我查询今天从杭州到上海往返的火车票,此外,再帮我查询一下上海今天的天气。"}, 
    {"role": "assistant", "content": "", "reasoning_content": "好的,用户今天需要从杭州到上海出差,当天往返,并且需要查询今天的火车票和上海的天气。首先,我需要确认用户提供的日期是否正确。用户提到今天是2025年3月17日,所以日期参数应该是当天。\n\n对于火车票查询,用户明确提到了出发地是杭州,目的地是上海,日期是今天,也就是2025-03-17。需要往返票,但函数参数里只需要start、end和date,所以可能需要两次查询?或者往返是否需要分开处理?不过根据函数描述,可能每次调用只能查单程,所以可能需要两次调用,但用户可能只需要一次查询往返的信息。不过根据提供的函数参数,必须包含start、end和date,所以可能需要调用一次,但往返可能需要分开处理。但用户可能希望往返的信息,但函数可能不支持往返参数,所以可能需要两次调用?或者可能函数返回的结果包含往返选项?不确定,但按照现有函数结构,可能需要先调用一次从杭州到上海的单程,然后返回时从上海到杭州的单程。因此需要两次调用train_tickets函数,分别查询去程和返程。\n\n不过用户可能只需要一次查询,但根据函数参数,可能需要两次。因此,首先处理去程:start是杭州,end是上海,date是2025-03-17。然后返程是start上海,end杭州,同样的日期。不过用户可能希望当天往返,所以需要确保返程的时间在当天。不过可能用户只需要一次查询往返,但根据函数设计,可能需要分开调用。\n\n接下来是天气查询,用户需要上海今天的天气,所以调用get_weather函数,area参数是上海。\n\n现在需要按照步骤调用这两个函数。首先处理火车票,两次调用,然后天气查询。但可能用户希望先处理火车票再去天气。所以先处理train_tickets两次,然后get_weather一次。不过需要确认是否必须所有参数都正确。例如,用户说当天往返,所以date都是同一天。所以两次train_tickets调用,参数分别是杭州到上海,和上海到杭州,日期都是2025-03-17。\n\n因此,首先生成两个tool_call,然后一个天气查询。", "tool_calls": [{"type": "function", "function": {"name": "train_tickets", "arguments": "{\"start\": \"杭州\", \"end\": \"上海\", \"date\": \"2025-03-17\"}"}}, {"type": "function", "function": {"name": "train_tickets", "arguments": "{\"start\": \"上海\", \"end\": \"杭州\", \"date\": \"2025-03-17\"}"}}, {"type": "function", "function": {"name": "get_weather", "arguments": "{\"area\": \"上海\"}"}}]}, 
    {"role": "tool", "content": "{\"costtime\": \"0小时48分\", \"trainno\": \"G7336\", \"arrivaltime\": \"8:34\", \"endstation\": \"上海南\", \"terminalstationcode\": \"SNH\", \"departstation\": \"苍南\", \"station\": \"杭州东\", \"departuretime\": \"7:46\", \"departstationcode\": \"CEH\"}", "name": "train_tickets"}, 
    {"role": "tool", "content": "{\"costtime\": \"1小时19分\", \"trainno\": \"C3629\", \"arrivaltime\": \"17:34\", \"endstation\": \"杭州西\", \"terminalstationcode\": \"YWH\", \"departstation\": \"上海虹桥\", \"station\": \"上海虹桥\", \"departuretime\": \"16:15\", \"departstationcode\": \"AOH\"}", "name": "train_tickets"}, 
    {"role": "tool", "content": "{\"area\": \"上海\", \"weather\": {\"areaid\": \"101020100\", \"temperature\": \"12\", \"weather\": \"晴\", \"wind_direction\": \"西风\", \"wind_power\": \"0-3级 微风  <5.4m/s\", \"weather_code\": \"00\"}}", "name": "get_weather"}, 
    {"role": "assistant", "content": "好的,以下是您今天(2025年3月17日)从杭州到上海的行程信息及天气情况:\n\n---\n\n### **火车票信息**\n#### **去程(杭州→上海)**\n- **车次**:G7336  \n- **出发站**:杭州东站(CEH)  \n- **出发时间**:7:46  \n- **到达站**:上海南站(SNH)  \n- **到达时间**:8:34  \n- **耗时**:48分钟  \n\n#### **返程(上海→杭州)**\n- **车次**:C3629  \n- **出发站**:上海虹桥站(AOH)  \n- **出发时间**:16:15  \n- **到达站**:杭州西站(YWH)  \n- **到达时间**:17:34  \n- **耗时**:1小时19分钟  \n\n---\n\n### **上海今日天气**\n- **温度**:12°C  \n- **天气**:晴  \n- **风力**:西风 0-3级(微风,<5.4m/s)  \n\n---\n\n### **建议**\n1. **行程安排**:  \n   - 去程建议提前1小时到杭州东站,返程注意上海虹桥站的乘车时间。  \n   - 上海虹桥站与上海南站距离较远,需预留足够换乘时间。  \n\n2. **天气提示**:  \n   - 白天晴朗但气温较低,建议携带外套和晴雨伞(备用)。  \n\n需要帮您进一步预订车票或提供交通接驳建议吗?", "reasoning_content": "好的,用户今天需要从杭州到上海出差,并且当天往返,还要查询上海的天气。首先,我需要整理一下用户提供的信息和工具返回的结果。\n\n用户给出的日期是2025年3月17日,今天。查询了杭州到上海的往返火车票,以及上海当天的天气。工具返回了两次火车票查询的结果和一次天气数据。\n\n首先看去程的火车票,杭州到上海的G7336次列车,出发时间是7:46,到达上海南站的时间是8:34,耗时48分钟。这趟车次应该很适合用户早上出发,当天就能到达上海开始工作。\n\n然后是返程的C3629次列车,从上海虹桥到杭州西,出发时间16:15,到达时间17:34,耗时1小时19分钟。用户可能需要在下午返回杭州,这样整个行程安排比较紧凑。\n\n接下来是天气情况,上海今天温度12度,天气晴朗,西风0-3级,微风。这样的天气适合出差,不会有降雨或大风影响出行。\n\n现在需要把这些信息以自然的方式呈现给用户。首先确认用户的需求是往返车票和天气,所以需要分点说明去程、返程和天气情况。要注意时间是否合理,比如用户是否能赶上返程的列车,是否有足够的停留时间。比如从早上8点到下午4点左右在上海,时间足够处理工作。\n\n另外,提醒用户注意天气情况,虽然天气晴朗,但温度较低,建议携带外套。同时,确认车次的出发和到达车站,确保用户知道具体站点,避免跑错车站。比如去程是杭州东到上海南,返程是上海虹桥到杭州西,可能需要提醒用户注意车站的不同,避免混淆。\n\n最后,总结一下信息,确保用户清楚所有安排,并询问是否需要进一步帮助,比如订票或交通建议。"}
]

2. Qwen-Agent

项目地址:https://github.com/QwenLM/Qwen-Agent

Qwen-Agent 封装了适配Qwen的工具调用模版,支持直接使用 OAI兼容API(例如vLLM部署)或DashScope API接口,为原生不支持工具调用的LLM推理接口提供了工具调用能力。


2.1 LLM接口:运行以下代码,完成一次含有工具调用的请求:

from qwen_agent.llm import get_chat_model
import os

# 获取封装的llm对象
llm = get_chat_model({
    'model': 'qwq-32b',
    # 使用dashscope API
    'model_type': 'qwen_dashscope',
    'api_key': os.getenv('DASHSCOPE_API_KEY')

    # 使用自己部署的类OpenAI API(例如vLLM部署):
    # 'model_server': 'http://localhost:8000/v1',  # api_base
    # 'api_key': 'EMPTY',
})

# 输入用户请求
messages = [{"role": "user", "content": "今天是2025年3月17号,我马上要去上海出差,当天往返,请帮我查询今天从杭州到上海往返的火车票,此外,再帮我查询一下上海今天的天气。"}]
while True:
    responses = []
    for responses in llm.chat(
                messages=messages,
                functions=TOOLS,
                stream=True
        ):
        pass
    print('\n[模型回复]: \n', responses)
    messages.extend(responses)
    tool_response = []
    for rsp in responses:
        if rsp.get('function_call'):
            tool_name = rsp['function_call']['name']
            tool_args = json.loads(rsp['function_call']['arguments'])
            tool_exec_res = TOOL_EXEC_MAP[tool_name](**tool_args)
            tool_response.append({'role': 'function', 'content': tool_exec_res, 'name': tool_name})
    print('\n[工具结果]: \n', tool_response)

    if tool_response:
        messages.extend(tool_response)
    else:
        break

以上代码完整对话消息为:

[
    {"role":"user","content":"今天是2025年3月17号,我马上要去上海出差,当天往返,请帮我查询今天从杭州到上海往返的火车票,此外,再帮我查询一下上海今天的天气。"},
    {"role":"assistant","content":"","reasoning_content":"好的,用户今天要从杭州到上海出差,当天往返,需要查询今天的往返火车票,还有上海的天气。首先,我需要确认用户提供的日期是2025年3月17日,所以日期参数没问题。\n\n先处理火车票查询。用户需要往返票,但提供的函数参数里只有单程的信息,可能需要分开查询去程和返程?不过函数可能只支持单程,所以可能需要两次调用?不过用户可能希望往返一起查,但根据工具描述,可能只能先查去程和返程两次。不过用户说当天往返,所以出发和到达城市需要反过来。第一次是杭州到上海,日期今天;第二次是上海回杭州,同样日期。但用户可能希望一次查询往返,但工具可能不支持,所以可能需要两次调用train_tickets函数,分别处理去和回的行程。不过用户的问题里可能只需要一次查询,但参数需要明确。所以可能需要先处理去程,然后返程。\n\n接下来是天气查询,用户需要上海今天的天气,所以调用get_weather函数,参数是上海。\n\n现在需要检查参数是否正确。对于火车票,start是杭州,end是上海,date今天。然后返程的话start是上海,end杭州,date同天。但用户可能希望往返,但工具可能不支持往返查询,所以需要两次调用。但用户的问题可能只需要一次查询,但可能需要分开两次。所以应该生成两个train_tickets的调用,分别去和回。但用户的问题可能只需要一次查询,但根据工具的参数,必须三个参数,所以两次调用。\n\n然后天气查询只需要一次,参数是上海。\n\n现在需要按照工具的格式生成调用。首先处理去程:杭州到上海,日期2025-03-17。然后返程:上海到杭州,同日期。再查询上海天气。\n\n所以需要三个tool_call,两次train_tickets和一次get_weather。"},
    {"role":"assistant","content":"","reasoning_content":"","function_call":{"name":"train_tickets","arguments":"{\\"start\\": \\"杭州\\", \\"end\\": \\"上海\\", \\"date\\": \\"2025-03-17\\"}"}},
    {"role":"assistant","content":"","reasoning_content":"","function_call":{"name":"train_tickets","arguments":"{\\"start\\": \\"上海\\", \\"end\\": \\"杭州\\", \\"date\\": \\"2025-03-17\\"}"}},
    {"role":"assistant","content":"","reasoning_content":"","function_call":{"name":"get_weather","arguments":"{\\"area\\": \\"上海\\"}"}},
    {"role":"function","content":"{\\"costtime\\": \\"0小时48分\\", \\"trainno\\": \\"G7336\\", \\"arrivaltime\\": \\"8:34\\", \\"endstation\\": \\"上海南\\", \\"terminalstationcode\\": \\"SNH\\", \\"departstation\\": \\"苍南\\", \\"station\\": \\"杭州东\\", \\"departuretime\\": \\"7:46\\", \\"departstationcode\\": \\"CEH\\"}","name":"train_tickets"},
    {"role":"function","content":"{\\"costtime\\": \\"1小时19分\\", \\"trainno\\": \\"C3629\\", \\"arrivaltime\\": \\"17:34\\", \\"endstation\\": \\"杭州西\\", \\"terminalstationcode\\": \\"YWH\\", \\"departstation\\": \\"上海虹桥\\", \\"station\\": \\"上海虹桥\\", \\"departuretime\\": \\"16:15\\", \\"departstationcode\\": \\"AOH\\"}","name":"train_tickets"},
    {"role":"function","content":"{\\"area\\": \\"上海\\", \\"weather\\": {\\"areaid\\": \\"101020100\\", \\"temperature\\": \\"12\\", \\"weather\\": \\"晴\\", \\"wind_direction\\": \\"西风\\", \\"wind_power\\": \\"0-3级 微风  <5.4m/s\\", \\"weather_code\\": \\"00\\"}}","name":"get_weather"},
    {"role":"assistant","content":"","reasoning_content":"好的,用户今天需要从杭州到上海出差并当天往返,所以需要查询往返的火车票和上海的天气。首先处理火车票的信息。\n\n用户提供的出发地是杭州,目的地上海,日期是2025年3月17日。我需要调用train_tickets两次,一次是杭州到上海,另一次是上海返回杭州。根据工具的参数要求,必须提供start、end和date,所以两次查询都填上日期。\n\n然后看返回的火车票信息。第一次查询杭州到上海的结果是G7336次列车,7:46从杭州东出发,8:34到达上海南,耗时48分钟。这趟车时间很合适,早上出发,很快就能到,适合出差上午的行程。\n\n第二次查询上海返回杭州的是C3629次,16:15从上海虹桥出发,17:34到达杭州西,耗时1小时19分。这趟车下午四点多出发,晚上七点前就能回到杭州,确保当天往返没问题。\n\n接下来是天气查询,用户需要上海当天的天气。调用get_weather,参数是上海。返回的数据显示温度12度,晴,西风0-3级,微风。这样的天气适合出行,但提醒用户可能早晚较凉,建议带件薄外套。\n\n现在需要把这些信息整理成用户易懂的回复。先列出往返的车次和时间,方便用户选择。然后说明天气情况,给出适当的建议。确保信息清晰,没有遗漏关键数据,比如车次号、时间、温度等。同时检查是否有其他需要注意的事项,比如是否需要提前取票或天气变化的可能性,但根据现有数据,可能不需要额外提醒。最后用友好的语气结束,让用户知道有其他需求可以继续询问。"},
    {"role":"assistant","content":"好的,以下是您需要的信息:\n\n### 上海往返杭州火车票查询结果:\n1. **杭州→上海(去程)**  \n   - 车次:G7336  \n   - 出发时间:07:46(杭州东站)  \n   - 到达时间:08:34(上海南站)  \n   - 时长:48分钟  \n\n2. **上海→杭州(返程)**  \n   - 车次:C3629  \n   - 出发时间:16:15(上海虹桥站)  \n   - 到达时间:17:34(杭州西站)  \n   - 时长:1小时19分  \n\n建议您根据行程安排选择以上班次,并提前完成购票。\n\n### 上海今日天气:\n- **温度**:12℃  \n- **天气**:晴  \n- **风力**:西风 0-3级(微风,<5.4m/s)  \n- **建议**:白天适合出行,但早晚稍凉,可携带薄外套。\n\n祝您出差顺利,往返安全!如需其他帮助,请随时告诉我。","reasoning_content":""}
]

至此,大家发现一次完整的带有工具调用的对话 = “模型推理”+“工具执行”+“模型推理”,并且这个算法过程需要在模型外部自行实现。

为此,Qwen-Agent还提供了一个接口`Agent`,将以上过程封装起来,用户只需传入模型和工具,即可自动编排以上过程并获得完整的对话消息。


2.2 Agent接口:运行以下代码,完成一次和Agent的交互:

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java