微信扫码
添加专属顾问
我要投稿
Qwen Agent如何兼容OpenAI工具调用?深入解析技术适配与实现方案。核心内容: 1. Qwen Agent与OpenAI工具调用的底层原理对比 2. 兼容性问题定位与解决方案 3. 代码层面的具体适配实现
过程中收到一些同学的反馈,认为qwen3等模型原生支持openai格式的tool调用,直接传tools即可,返回结果中的tool_calls字段里有调用工具的指令。说的也没错。不过,不管是qwen3还是GPT-4o等支持传tools的大模型,每次的工具调用本质都是通过prompt+多轮交互驱动(第一轮返回工具调用指令+第二轮插入调用结果并回答)。openai格式封装的底层tools传参以及返回值中的tool_calls,本质和Qwen Agent类似,只是在接口内部定义了一套工具解析器,是一种工程化实现手段。所谓的原生支持,更多的是指"返回工具调用指令"的能力。
基于这个反馈,我考虑在Qwen Agent上尝试使用支持传tools的大模型。发现Qwen Agent的工具解析引擎并未适配这种情况。如下图所示,给Qwen Agent配置了一个image_gen工具,image_gen是个外部api,能基于prompt绘图并返回图像链接。正常情况下,模型能构造输入prompt参数并调用工具。此处模型在thinking后直接卡住,未正常调用工具,无法继续运行。
我们查看oai接口的返回数据,可以看到指令调用在tool_calls字段中正常返回,但qwen agent未正确解析,导致运行卡住。
究其原因,是因为Qwen Agent支持的大模型response中的tool_calls相关指令是在'content'字段返回的,通过Agent框架封装的单独工具解析器来适配。而qwen3等已经做了一层工程化封装tool_calls的模型,其参考openai的标准格式,返回的调用指令单独存放在“tool_calls”字段,造成Qwen Agent的解析失效。
从代码中也能发现,仅处理了content和reasoning_content相关字段,未处理tool_calls相关字段。
一种思路是如果存在tool_calls字段,则将相应工具调用指令提取出来,和content内容拼接在一起,适配Qwen Agent的解析引擎,这样后续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的格式:<tool_call>\n{tool_call_json}\n</tool_call>
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:
# 尝试解析arguments为JSON对象
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)
returnf"\n<tool_call>\n{tool_call_json}\n</tool_call>", True
except json.JSONDecodeError:
# 如果解析失败,使用空对象
tool_call_obj = {"name": function_name, "arguments": {}}
tool_call_json = json.dumps(tool_call_obj, ensure_ascii=False)
returnf"\n<tool_call>\n{tool_call_json}\n</tool_call>", 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字段封装tool_calls,都是一种工具调用和解析方法,可以有不同的实现,也可以统一抽象成1种方法。本文演示了如何统一到Qwen Agent的解析器:单独获取openai格式的tool_calls字段然后拼接到content字段中,再运行qwen agent统一的工具解析器即可。不过这种方法只是一种workaround,相当于叠加了2次工具解析,理想情况下有更通用和顶层设计,兼容所有类型的模型,openai的tools传参和返回tool_calls也未必是一种很好的设计。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-05-29
2025-05-23
2025-04-29
2025-05-07
2025-05-07
2025-05-07
2025-06-01
2025-04-29
2025-06-07
2025-05-20
2025-07-19
2025-07-19
2025-07-19
2025-07-19
2025-07-19
2025-07-18
2025-07-18
2025-07-18