博客配套代码发布于github:03 Function calling 与工具调用
本系列所有博客均配套Gihub开源代码,开箱即用,仅需配置API_KEY。
如果该Agent教学系列帮到了你,欢迎给我个Star⭐,非常感谢!
知识点:Function calling原理、工具函数封装、API接入实践、多轮调用流程、Agent能力扩展
一、什么是function callings
回想一下,所谓ai,虽然它的确看起来牛哄哄,什么都懂什么都会,但它的局限性非常大。它无法知道最新发生的某件事,无法知道你公司数据库的重要信息,也没办法动手帮你干事情。这时候,而解决方法也显而易见:为它加上手,也就是function calling。
function是函数,calling是调用,即从外部调用函数给agent。
上面看起来是局限的事情,有了调用函数的方法后立刻变得简单了起来。不知道某事就为它加入个可联网搜索的api接口,不知道你公司数据库信息就用函数链接数据库,没法动手做事就写代码让它动起来...
总而言之,当我们为它配备一个函数库,原先那些它无法解决的事情就会通过调用函数库的方式来完成回应。此时的大模型处理与解决问题的能力可以说是又上一层楼。
function calling就是Agent智能体开发的基础。
二、自定义函数调用
我们先来尝试一下,自己写个假的小函数作为工具让Agent调用:
def get_weather(location):
# 模拟获得天气信息
return f"{location}当前天气:23℃,晴,风力2级"
封装完了之后,我们还需要将其改一下agent接受的固定格式:
# 以下格式为固定写法,一般仅需改description与name
get_weather_func = {
"name": "get_weather", # 函数名称
"description": "获取指定城市的天气情况", # 对该函数的描述
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string", # 该参数类型
"description": "城市名称,如北京、上海等" # 对该参数的描述
}
},
"required": ["location"] # 声明必填
}
}
看起来很复杂,但里面格式都是固定的。其中需要更改的地方只有name,description,parameters与参数location。最后的required就是要哪些参数加进去。
封装好get_weather_func后就可以将它扔给tools作为一个工具了。
# 固定写法,有多少个tool就往里追加多少个
tools = [
{
"type": "function",
"function": get_weather_func
}
]
紧接着,我们就需要让response接收这个工具:
def chat_loop(agent_client, tools):
messages = [
{"role": "system", "content": "你是一个善解人意会热心回答人问题的助手。如果你感觉你回答不了当前问题,就会调用函数来回答。"},
{"role": "user", "content": "北京今天的天气怎么样?"}
]
response = agent_client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools, # 调用工具
tool_choice="auto" # 模型自主选择是否调用工具
)
message = response.choices[0].message
对比起之前,很明显能注意到这里我们加入了tools=tools,tool_choice="auto"。这里是让agent知道可以调用agent,auto是让agent智能选择它是否要调用工具来解决问题。
再接着想想:
ai调用工具与否肯定按两套方式输出:没调直接输出,调用了就按调用后的输出。但是ai返回肯定是第一次只能返回它是否要调用,第二次才能返回它依靠调用后的工具函数输出的结果。所以这里我们要二次调用才能最终生成答案。因此代码应构建如下:
# 如果有该参数,证明ai调用了工具
if message.tool_calls:
# 对每个可能要调用的工具进行循环
for tool_call in message.tool_calls:
if tool_call.function.name == "get_weather":
# 解析参数
args = json.loads(tool_call.function.arguments) # 获取用户关键词的参数
location = args.get("location", "未知地点")
# 调用真实参数
weather_info = get_weather(location)
# 将函数执行结果以"tool"角色传给模型,等待后面二次调用
messages.append(message) # 先添加模型的原始响应
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"content": weather_info
})
# 第二次调用,让模型基于工具返回的结果再生成最终答案
final_res = agent_client.chat.completions.create(
model="deepseek-chat",
messages=messages
)
print('已调用工具...')
print(f'回答:{final_res.choices[0].message.content}')
else:
# 模型没有要调用工具,直接返回
print('未调用工具...')
print(f'回答:{response.choices[0].message.content}')
(完整可运行代码于github配套提供)
返回结果:
没有问题。
总结一下我们的流程:
User Input
↓
LLM (第一次调用) → 决定调用哪个工具
↓
执行函数(get_weather)
↓
把结果以 "tool" 角色塞回去
↓
LLM (第二次调用) → 生成最终回答
三、API调用
上面的毕竟是我们自己虚构的函数,这里我们接入一个真实存在的api来测试下。
与上述的流程大同小异,只不过需要注意下你新加工具的api的文档使用方法,传入api与使用时的格式也要小心。
ipapi这个网站能获取ip地址与对应城市,并且不需要api-key,直接向对应地址发起请求即可:
def get_addr():
res_ip = requests.get('https://ipapi.co/ip/').text
res_city = requests.get('https://ipapi.co/city/').text
return f'ip地址:{res_ip},所在城市:{res_city}'
另外有些小地方也需要注意下。这个get_addr特殊在它不需要加入参数。
if tool_call.function.name == "get_addr":
# 解析参数
# args = json.loads(tool_call.function.arguments) # 获取用户关键词的参数
# location = args.get("location")
# 调用真实参数
# your_info = get_weather(location)
# 这里无参数,直接调用函数就行
your_info = get_addr()
get_addr_func = {
"name": "get_addr", # 函数名称
"description": "获取用户的ip地址与城市", # 对该函数的描述
"parameters": {
"type": "object",
"properties": {}, # 参数为空,那么这两个地方也是空的
"required": []
}
}
运行结果如下:
完成。
俩个案例做下来,有没有发现这个过程是不是有点繁琐,而且里面的很多工具看起来都是可以封装的?
猜对了,这就是langchain的做法。下篇我们很快会开始了解langchain,这篇我们本质上就是在学function calling的原理,造轮子的底层逻辑。在框架langchain中,它就已经给我们封装好了这些复杂的东西,不需要我们再手动去敲,直接上手去做即可。
四、总结
知识点概括:Function calling原理、工具函数封装、API接入实践、多轮调用流程、Agent能力扩展
至此我们可以自豪的说,我们已经知道如何真正构建一个正儿八经的智能体了,它有记忆和提示词,可以调用外部工具函数,能够多轮问答。
但,有没有觉得这些工作还是有点繁琐?是的,基础篇我们已经搞定,接下来就是框架篇了。langchian/langgraph会为我们的agent开发更省功夫,并给出更系统化、标准化、自动化的使用方法。同时还有rag让我们的agent更为智能。
下篇内容,我们会开始深入讲解langchain与其详细用法,更快捷的构建一个agent。
🚀一键跳转:[Ai Agent] 04 一文吃透LangChain:Prompt、LLM、Chain、Memory 全流程实战
📌 项目代码 + 后续案例合集 全部发布在 Github 仓库 agent-craft ,持续更新中,欢迎Star⭐!