游乐游手机版
首页/AI热点日报/热点详情

Qwen Agent函数调用兼容OpenAI工具调用改造

类型:热点整理2026-07-19
针对QwenAgent无法解析OpenAI格式模型返回的tool_calls字段问题,提出将工具调用指令提取并拼接到content字段的兼容方案,使Agent框架正常识别并执行工具调用,实现跨格式适配。

Qwen Agent 兼容 OpenAI 工具调用的完整方案:技术适配与实现路径

在实际项目落地过程中,许多开发者都会陷入一个经典的“双轨制”困境:一方面,Qwen Agent 自身拥有成熟的工具调用解析机制;另一方面,OpenAI 格式下的 tool_calls 字段已经逐渐成为行业通用标准。当你在 Qwen Agent 上使用支持 tools 参数的大模型(例如 qwen3 或 GPT-4o)时,往往会发现 Agent 框架无法正确解析返回的 tool_calls 指令,从而导致工具调用流程中断。这背后的根本原因是什么?我们又该如何解决?本文将一步步拆解问题,并提供可直接复现的代码方案。

Qwen Agent

从用户反馈说起

此前有同行反馈,qwen3 等模型原生支持 OpenAI 格式的 tool 调用,只要直接传入 tools 参数,返回结果中的 tool_calls 字段就会包含调用指令。这个说法本身没有错误,但需要澄清一点——无论 qwen3 还是 GPT-4o,每次工具调用的本质仍然是 prompt 加多轮交互驱动:第一轮返回工具调用指令,第二轮插入调用结果并生成回答。OpenAI 格式封装的 tools 传参以及返回的 tool_calls,本质上是接口内部定义的一套工具解析器,属于工程化实现手段。所谓“原生支持”,更准确地说是指模型具备“返回工具调用指令”的能力,底层机制并没有本质区别。

正是基于这个思考,我们尝试在 Qwen Agent 上直接使用支持 tools 参数的大模型,结果发现 Agent 的工具解析引擎并未适配这种情况。以下图为例:给 Qwen Agent 配置了一个 image_gen 工具(外部 API,基于 prompt 绘图并返回图像链接),正常情况下模型会构造输入 prompt 参数并调用工具。但实际运行中,模型在 thinking 之后直接卡住,工具没有被正常调用。

查看 OpenAI 接口的返回数据,可以发现工具调用指令确实在 tool_calls 字段中正常返回了,但 Qwen Agent 没有正确解析,导致运行中断。

问题定位:解析器不匹配

根本原因在于,Qwen Agent 支持的大模型,其 response 中的工具调用指令默认是在 content 字段中返回的,Agent 框架通过单独的工具解析器来提取和执行。而 qwen3 等模型已经做了一层工程化封装——参考 OpenAI 标准格式,把调用指令单独放在 tool_calls 字段中。这样一来,Qwen Agent 原有的解析逻辑(只处理 content 和 reasoning_content 字段)就失效了。

从代码层面也能直接看出,原生实现中没有处理 tool_calls 相关字段。

兼容方案:将 tool_calls 拼接到 content 中

思路并不复杂:如果 response 中存在 tool_calls 字段,就将其中的工具调用指令提取出来,按照 Qwen Agent 约定的格式拼接到 content 内容里。这样后续的解析引擎就能正常识别并触发实际调用和后续多轮交互。

具体实现上,在 oai.py 的相关位置添加以下代码:

content = response.choices[0].message.content or ""

def _has_tool_calls(self, obj, attribute='tool_calls') -> bool:
    return hasattr(obj, attribute) and getattr(obj, attribute)

# 解析tool_calls字段的调用指令,构造成qwen agent的格式:\n{tool_call_json}\n
def _format_tool_call(self, tool_call=None, tool_call_data: Dict = None) -> Tuple[str, bool]:
    function_name = None
    arguments = None
    
    # 从tool_call对象中提取函数名和参数
    if tool_call is not None and hasattr(tool_call, 'function') and tool_call.function:
        function_name = getattr(tool_call.function, 'name', '')
        arguments = getattr(tool_call.function, 'arguments', '')
    # 从tool_call_data字典中提取函数名和参数
    elif tool_call_data is not None:
        function_name = tool_call_data.get('name', '')
        arguments = tool_call_data.get('arguments', '')
    
    if function_name is None or arguments is None:
        return "", False
    
    function_name = function_name or ""
    arguments_str = arguments if arguments else '{}'
    
    try:
        args_obj = json.loads(arguments_str)
        tool_call_obj = {"name": function_name, "arguments": args_obj}
        tool_call_json = json.dumps(tool_call_obj, ensure_ascii=False)
        return f"\n\n{tool_call_json}\n", True
    except json.JSONDecodeError:
        tool_call_obj = {"name": function_name, "arguments": {}}
        tool_call_json = json.dumps(tool_call_obj, ensure_ascii=False)
        return f"\n\n{tool_call_json}\n", True

# 实际使用时,拼接到content中
if self._has_tool_calls(response.choices[0].message):
    for tool_call in response.choices[0].message.tool_calls:
        tool_call_str, success = self._format_tool_call(tool_call=tool_call)
        if success:
            content += tool_call_str  # 以固定格式拼接到content中

修改后的代码效果如下(展示了新增的 tool_calls 处理逻辑)。

更新后的实际效果

适配解析器格式后,以 qwq-32b 为例,工具调用可以正常工作了。

第三次调用时虽然报错了,但模型经过反思后进行了自我纠正,最终完成调用。

总结

无论通过 OpenAI 底层的 tool_calls 字段,还是 Qwen Agent 基于 content 字段封装工具调用,本质上都是工具解析与调用的方法。两种实现完全可以通过统一的抽象来兼容。本文演示的方案,是将 OpenAI 格式的 tool_calls 字段单独提取出来,拼接到 content 字段中,再交由 Qwen Agent 的通用解析器处理。当然,这只是一种 workaround,相当于叠加了两层解析。更理想的方案应该从顶层设计上兼容所有类型的模型——毕竟 OpenAI 的 tools 传参和返回 tool_calls 也未必是最优解。期待后续社区能给出更通用的适配方案。

来源:https://www.53ai.com/news/LargeLanguageModel/2025072091643.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。