Qwen3实现函数调用。

项目链接:GitHub - muggle-stack/Function_calling: LLM Function Calling

部署

首先安装依赖:

pip install ollama webbrowser

我是用的ollama的 API,也可以用openai的或者Anthropic的,即使是相同的模型不同厂家调用tools以后API输出的字段不同,ollama的字段是这样的:

做function call的时候需要根据字段提取tool calls(当然不是ollama的话也不一定叫tool calls,不过大差不差)

克隆代码:

git clone https://github.com/muggle-stack/Function_calling.git

代码讲解

main程序

项目中有两个模型,虽然都是qwen3:0.6b,但是功能不同

llm_model = LLMModel(llm_model_path='qwen3:0.6b', stream=True)
fc_model = FCModel(tools, func_map, fc_model_path='qwen3:0.6b')

类LLMModel初始化的时候默认模型是qwen3:0.6b,可以自行修改。默认是流输出的方式输出文字,也可以自己改为false。

类FCModel(Function call model)初始化的时候默认会传入tools(就是自己定义的工具),func_map是函数名表。这两个参数在functions.py文件里面,在main.py里面这样去导入:

from functions import tools, func_map

 主程序中是一个循环:

try:
    while True:
        text = input("请输入内容:")
        t1 = time.time()
        function_called = fc_model.generate(text)
        print('used time:', time.time() - t1)
        if function_called:
            continue

        llm_output = llm_model.generate(text)
        for output_text in llm_output:
            print(output_text, end='', flush=True)
        print()
except KeyboardInterrupt:
    print("process was interrupted by user.")

主要作用是输入内容以后先通过第一个模型(FCModel)判断你输入的prompt是否存在函数,如果存在,则会触发tool calls,执行相应的函数,然后返回bool类型数据为true,当if检测到function_called为true的时候,重新回到循环,等待你的下一次输入。否则,function_called 为 false,则经过第二层,通用模型,通用模型则会正常回答问题。

function_model.py

初始化

def __init__(self, tools, func_map, fc_model_path='qwen3:0.6b'):
    self._func_map = func_map
    self._model_path = fc_model_path
    self._tools = tools
    self.messages = [
        {
            "role": "system",
            "content": "你是一个工具助手,严格执行用户定义的tools,严格返回用户在tools设定的函数名和设定的tools参数;",
        }
    ]

工具调用方法

def get_chat_stream(self, text, messages):
    messages.append({"role": "user", "content": text})
    stream = chat(
        model=self._model_path,
        messages=messages,
        tools=self._tools
    )
    return stream

text就是传进来的“你的问题”,message包括两个部分,一个是系统的system prompt,另一个则是你的输入content。把系统的的messages append到你的messages后面得到完整的messages通过ollama的chat方法传参messages,tools参数则是你初始化的时候的tools。当然用tools以后就不能流式输出了。

提取大模型回答中的tool_calls对象

def generate(self, text):
    response = self.get_chat_stream(text, self.messages)
    # print("response:", response)
    msg = response.message

    if msg.tool_calls and msg.tool_calls is not None:
        tc = msg.tool_calls[0]           # 取第一个 ToolCall
        fn = tc.function                  # Function 对象
        fn_name = fn.name                 # 函数名
        fn_args = fn.arguments or {}      # 参数字典

        function_to_call = self._func_map.get(fn_name)
        if function_to_call:
            if has_parameters(function_to_call):
                result = function_to_call(**fn_args)
            else:
                result = function_to_call()
            print('Function output:', result)
        else:
            print('Function not found:', fn_name)
        return True
    else:
        return False

我注释的print加回来可以看到模型的输出或者你看我上面的图,你就知道ollama API的输出是什么格式了。你得提取函数的名字还有参数(如果有的话)has_parameters函数主要是用来判断tool_calls有没有参数,如果有参数就解包拿参数。

llm_model.py

通用模型就比较简单了,和上面用法差不多,就是开启流输出就行了:

def get_chat_stream(self, text, messages):
    messages.append({"role": "user", "content": text})
    stream = chat(
        model=self._model_path,
        messages=messages,
        stream=self._stream
    )
    return stream

当然,你流输出你想要在main.py里面去拿到需要一个生成器(yield),每生成一个 token 就迭代一次:

# 处理聊天流中的每一部分
for chunk in stream:
    content = chunk['message']['content']
    yield content

functions.py

在这里面你可以定义你的函数具体怎么使用:

def open_browser():
    url = "https://www.baidu.com"
    webbrowser.open(url)
    return f"已打开浏览器,访问 {url}"

def turn_off_light():
    return "灯已关闭"

def turn_on_light(brightness:int):
    return f"灯已调亮{brightness}"

def turn_left(angle:int):
    return f"左转{angle}度"

这四个函数是我的示例,实际上都能被执行,但是下面三个只是返回打印,第一个open_browser能够执行打开浏览器的操作。

tools的格式需要注意,qwen官方给出的格式是怎样,你就得怎样:

tools = [
    {
        "type": "function",
        "function": {
            "name": "turn_on_light",
            "description": "开灯或者turn on light,调高多少亮度",
            "arguments": {
                "angle": {
                    "type": "int",
                    "description": "调高的亮度"
                }
            }
        },
    },
    {
        "type": "function",
        "function": {
            "name": "turn_off_light",
            "description": "关灯或者turn off light",
        }
    },
    {
        "type": "function",
        "function": {
            "name": "turn_left",
            "description": "左转一定角度",
            "arguments": {
                "angle":{
                    "type": "float",
                    "description": '旋转的角度值',
                }
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "open_browser",
            "description": "打开浏览器",
        }
    }
]

因为他们是按照这个格式训练的模型,函数调用的细节文档可以看下:函数调用 - Qwen

展望 

后续会出模型量化教程,还有CV的分类、跟踪等任务的前后处理。mcp如何手搓客户端(不是用anthropic的desktop)以及mcp server等,还有一些推理框架的技术细节。

点个赞呗,点个赞呗,点个赞呗,谢谢。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐