一、前言
1.搞明白什么是函数调用(工具调用)?
简单总结就是:大模型通过理解用户输入的自然语言,判断是否需要调用外部函数,并生成符合函数要求的参数(如JSON格式)。
- 步骤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我们拿来即用。