Qwen-Agent实现Deepseek函数调用(Function Calling)

一、前言

1.搞明白什么是函数调用(工具调用)?

简单总结就是:大模型通过理解用户输入的自然语言,判断是否需要调用外部函数,并生成符合函数要求的参数(如JSON格式)。

2.函数调用流程
  • 步骤1:用户输入自然语言请求。
  • 步骤2:大模型分析请求,决定是否需要调用外部函数。
  • 步骤3:若需要,模型生成结构化参数(如JSON)。
  • 步骤4:开发者用这些参数调用对应的函数。注意:是我们拿到模型返回的函数名称后,程序里面实现的调用(并不是大模型本身去调用)
  • 步骤5:将函数返回的结果整合到大模型的回复中。

总结:通过两次与大模型交互得到最终结果,第一次大模型根据问题判断如果能直接回答就直接回答,如果不能回答,根据提供的工具描述是否能通过工具调用获取答案,这一步大模型会返回需要调用的工具名。第二次我们根据大模型返回结果,如果需要调用工具,我们程序中实现调用,将结果以及之前上下文一块交给大模型等待结果返回,如果不需要工具调用,那就直接返回第一次调用的结果。

这时大伙可能会有疑问,针对大模型不能回答的问题它是怎么做出返回调用工具的结构化参数,而不是直接返回无法回答呢?实现这一过程关键在于提示工程 (Prompt Engineering) ,这也是实现函数调用的关键。

Prompt具体什么样呢?后续可以出一篇手动实现函数调用的文章详细说明一下Prompt怎么约束大模型的。这次就用通义千问帮我们做的框架Qwen-Agent,Qwen2预先训练了多种支持函数调用的模板,可以直接利用这一过程。

有了这个框架我们可以很容易的实现函数调用,包括目前还不支持函数调用的Deepseek大模型。

二、Qwen-Agent框架

官网参考文档:函数调用 - Qwen

文档里写的非常详细了,我这里就直接给出基于该框架实现Deepseek大模型函数调用代码:

import json

from qwen_agent.llm import get_chat_model

import select_tool

# ailiyun 百炼平台实现模型调用
llm = get_chat_model({
    "model": "deepseek-r1",
    "model_server": "https://dashscope.aliyuncs.com/compatible-mode/v1",
    "api_key": "替换成你的key",
})

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "weather_query_results_json",
            "description": "支持查询地区的天气数据",
            "parameters": {
                "type": "object",
                "properties": {
                    "area_name": {
                        "type": "string",
                        "description": '地区名称',
                    },
                    "date": {
                        "type": "string",
                        "description": '时间,格式为YYYY-MM-DD',
                    },
                },
                "required": ["date"],
            },
        },
    },
]

# 创建查询函数工具
weather_query = select_tool.WeatherQuery()
# 工具列表
tools = [weather_query]

functions = [tool["function"] for tool in TOOLS]

print(functions)


def agent_query(input):
    MESSAGES = [
        {"role": "system", "content": "You are a helpful assistant."},
    ]
    messages = MESSAGES[:]
    message = {"role": "user", "content": input}
    messages.append(message)
    final_response = ""
    for responses in llm.chat(
            messages=messages,
            functions=functions,
            extra_generate_cfg=dict(parallel_function_calls=True),
    ):
        print(responses)
        pass
    messages.extend(responses)

    for message in responses:
        # 解析返回信息中是否需要进行接口调用
        if fn_call := message.get("function_call", None):
            fn_name: str = fn_call['name']
            fn_args: dict = json.loads(fn_call["arguments"])
            print(f'调用的接口名称:{fn_name}')
            print(f'调用的接口参数:{fn_args}')
            current_tool = None
            fn_ret = ''
            for tool in tools:
                if tool.name == fn_name:
                    current_tool = tool
                    break
            if current_tool:
                try:
                    fn_ret = current_tool.query(fn_args)
                except Exception as e:
                    fn_ret = f"{e}"
                else:
                    fn_ret = str(fn_ret)
            print(f'调用的接口返回:{fn_ret}')
            current_message = {
                "role": "function",
                "name": fn_name,
                "content": fn_ret,
            }
            messages.append(current_message)

    # print(messages)

    for responses in llm.chat(messages=messages, functions=functions):
        pass
    print('=============llm 推理内容=============')
    # 获取推理内容
    print(responses[0]["reasoning_content"])
    print('=============llm 回答内容=============')
    # 获取结果内容
    print(responses[1]["content"])
    # 将本次对话封装到message
    messages.append({'role': 'assistant', 'content': responses[1]["content"]})
    # print(messages)

while True:
    query = input('query:')
    agent_query(query)

工具类代码:我这里就固定返回一条假数据了

class WeatherQuery(object):
    # 工具名称
    name: str = "weather_query_results_json"

    # 工具查询方法
    def query(self, input):
        # 请求体数据
        headers = {

        }

        # 发送 POST 请求
        # response = requests.post("http://127.0.0.1:8001/llm/weather", json=input, headers=headers)

        # return response.text

        return '{"最高温度":"22℃","最低温度":"12℃"}'

回答结果:

调用的接口名称:weather_query_results_json
调用的接口参数:{'area_name': '北京市', 'date': '2025-04-08'}
调用的接口返回:{"最高温度":"22℃","最低温度":"12℃"}
=============llm 推理内容=============
好的,用户让我查询北京市2025年4月8日的天气数据。首先,我需要确认用户提供的日期是否符合格式要求,也就是YYYY-MM-DD。用户给出的日期是2025-04-08,这看起来是正确的格式。
接下来,我需要调用天气查询函数weather_query_results_json,并传入参数area_name为“北京市”,date为“2025-04-08”。这里需要注意的是,虽然函数定义中date是必填项,但area_name是否必须呢?根据提供的函数签名,required字段只包含date,所以area_name是可选的。不过用户明确提到了北京市,所以应该填写进去。
然后,工具返回了最高温度22℃,最低温度12℃。我需要将这些信息以用户友好的方式呈现出来。用户可能想知道当天的天气概况,是否需要穿衣建议?但用户没有特别提到,所以可能只需要简单报告数据即可。
另外,考虑到日期是未来的,2025年距离现在还有一段时间,天气数据可能存在不确定性。可能需要提醒用户这是预测数据,实际天气可能会有所变化。不过工具返回的数据已经存在,可能系统有长期预测的能力,或者假设数据是准确的。
最后,组织回答的结构:先确认用户的需求,然后提供温度范围,并保持语言简洁明了。是否需要补充其他信息,比如天气状况(晴、雨等)?但工具响应中没有提供这些数据,所以无法加入。因此,只报告最高和最低温度即可。
=============llm 回答内容=============
以下是北京市2025年4月8日的天气预报数据:
🌤️ 当日温度范围:
▸ 最高气温:22℃
▸ 最低气温:12℃
昼夜温差较大,建议采用洋葱式穿衣法搭配外套出行。

测试下来该框架整合的效果非常不错,针对这种简单的单一性问题查询准确率还挺高的。但复杂问题涉及到调用多个工具也会存在偏差。优势在于方便,框架封装好了Prompt我们拿来即用。

### 比较 DeepSeek-R1-Distill-Qwen-14B 和 DeepSeek-R1-Distill-Qwen-14B-GGUF #### 参数量与模型结构 DeepSeek-R1-Distill-Qwen-14B 是基于 Qwen 架构的大规模预训练语言模型,参数量达到 140亿。该模型通过蒸馏技术优化,在保持性能的同时降低了计算资源需求[^1]。 相比之下,DeepSeek-R1-Distill-Qwen-14B-GGUF 版本同样拥有相同的架构基础和相似的参数数量,但是经过 GGUF (General Graph-based Unified Format) 技术处理,使得模型文件更紧凑高效,适合边缘设备部署。 #### 文件格式与存储效率 标准版 DeepSeek-R1-Distill-Qwen-14B 使用常见的权重保存方式,而 GGUF 格式的变体则采用了图结构化数据表示方法来压缩模型尺寸并提高加载速度。这种改进对于内存有限或带宽受限环境特别有利。 #### 推理性能对比 由于GGUF版本进行了针对性优化,因此在某些硬件平台上可能会表现出更好的推理延迟特性;然而具体表现取决于实际应用场景以及所使用的加速库等因素影响。通常情况下两者的核心算法逻辑一致,主要区别在于实现细节上的不同。 ```python import torch from transformers import AutoModelForCausalLM, AutoTokenizer def load_model(model_name): tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name) return model, tokenizer model_standard, tokenizer_standard = load_model("deepseek-ai/DeepSeek-R1-Distill-ai/DeepSeek-R1-Distill-Qwen-14B-GGUF") text = "Once upon a time" input_ids_standard = tokenizer_standard(text, return_tensors="pt").input_ids output_standard = model_standard.generate(input_ids_standard) input_ids_gguf = tokenizer_gguf(text, return_tensors="pt").input_ids output_gguf = model_gguf.generate(input_ids_gguf) print(f'Standard Model Output: {tokenizer_standard.decode(output_standard[0], skip_special_tokens=True)}') print(f'GGUF Model Output: {tokenizer_gguf.decode(output_gguf[0], skip_special_tokens=True)}') ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旅行程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值