“函数调用”或“工具调用”(可以互换使用)是指大型语言模型(LLM)能够生成与提示中的上下文相关的格式化的文本输出,以便开发人员可以通过方法调用或API调用来调用系统中的其他部分。
专属的LLM提供商通过一个单独的tools:
参数以及发送到API的messages:
数组,让您的工具轻松对接到LLM。工具通常以Open API规范定义,并采用JSON格式。在幕后,LLM提供商将消息、工具以及如果有的系统提示结合在一起。
在使用开源的大语言模型(LLM)时,实现函数调用有两种方式。如果该LLM不原生支持函数调用,则可以结合提示工程、微调和受限解码等方式来实现。而那些原生支持函数调用的LLM则通过特殊的token来实现这一功能。
比如下面的例子中,一个开源的大语言模型(LLM)不自带原生支持,我们通过提示工程来实现函数调用。这在去年是很常见的提示技巧。
你可以使用以下工具:
{tools}
使用以下格式:
问题:你必须回答的输入问题
思考:你应该考虑怎么做
行动:应该采取的行动,应为{tool_names}之一
行动输入:采取该行动所需的输入
如果LLM选择调用一个工具的话,它会返回如下格式的文本,该文本可以使用正则表达式匹配:
返回的将是如下格式的文本:
具体的文本格式示例
操作:天气
地点:旧金山,加利福尼亚
使用这种方法,开源模型的表现还算可以。如果你想取得更好的、更一致的结果,你得加上微调和约束解码这两个步骤。
不过,通过使用原生支持函数调用的大型语言模型(LLMs),可以实现更好的性能,这些模型是专门训练来进行函数调用的。有效地训练模型进行函数调用需要使用模型的分词器来识别这些包围工具调用的特殊标记。
比如说,mistralai/Mistral-7B-Instruct 的词元化器定义并使用了以下特殊 token:
[AVAILABLE_TOOLS][/AVAILABLE_TOOLS]
[TOOL_CALLS]
(缺少闭合标签。)[TOOL_RESULTS][/TOOL_RESULTS]
目前这方面的标准化还不够完善,不过NousResearch/Hermes-2-Theta-Llama-3–8B 这个项目定义了一些特殊标记。
-
<tools></tools>
-
ICICIC
<tool_response></tool_response>
这些模型已经被训练和/或调整,以便它们能够识别并利用这些标记。
我们将看看模型希望我们的提示如何组织,并使用这些 token 来实现带工具调用的多轮对话交流。
我们将专注于前面提到的Mistral-7B-Instruct模型,但我鼓励你也试试伯克利功能调用排行榜(https://gorilla.cs.berkeley.edu/leaderboard.html)上的其他支持功能调用的模型。
开始设置我们将使用Ollama来运行我们的开源大语言模型。你可以轻松地按照其instructions进行本地设置。
假设我们有这个 get_current_weather()
函数
{
"type": "函数",
"函数": {
"name": "获取当前的天气",
"description": "获取当前的天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如旧金山,加利福尼亚"
},
"format": {
"type": "string",
"enum": [
"摄氏度",
"华氏度"
],
"description": "使用的温度单位(例如摄氏度或华氏度)"
}
},
"required": [
"location",
"format"
]
}
}
}
我们将把这个功能公开在提示中,这样大型语言模型(LLM)可以随意调用它。我们将通过一系列API请求和响应,来看看提示需要怎样的结构。
在第一次请求时,我们发送一个包含函数定义和用户消息的提示,这些内容被 [INST][/INST]
标签包裹。原始模式(raw mode)可以防止 Ollama 对提示应用模板。了解更多详情,请参阅原始模式。
第一轮:
curl --location "http://localhost:11434/api/generate" \
--header "Content-Type: application/json" \
--data "{
\"model\": \"mistral:7b-instruct-v0.3-fp16\",
\"prompt\": \"[AVAILABLE_TOOLS][{'type': 'function', 'function': {'name': 'get_current_weather', 'description': '查询当前天气', 'parameters': {'type': 'object', 'properties': {'location': {'type': 'string', 'description': '城市和州,例如 San Francisco, CA'}, 'format': {'type': 'string', 'enum': ['celsius', 'fahrenheit'], 'description': '要使用的温度单位。'}}, 'required': ['location', 'format']}}}][/AVAILABLE_TOOLS][INST]今天巴黎的天气如何?[/INST]\",
\"stream\": false,
\"raw\": true
}"
{
"response": "[TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '法国巴黎', 'format': '℃'}}]"
}
模型决定在调用 get_current_weather
函数时,在其前面加上 [TOOL_CALLS]
,并带上正确的参数。请注意,这里没有 [/TOOL_CALLS]
结束标签。这个闭合标签不在model词汇表中。
我们提取工具调用并在我们的系统中运行它。
get_current_weather(location: '巴黎', format: '摄氏')
=> 25C
我们将工具调用的过程及其输出,用 [TOOL_RESULTS][/TOOL_RESULTS]
标记起来,再加回提示字符串中,然后再次调用该模型。
第二轮:
curl --location "http://localhost:11434/api/generate" \
--header "Content-Type: application/json" \
--data "{
\"model\": \"mistral:7b-instruct-v0.3-fp16\",
\"prompt\": \"[AVAILABLE_TOOLS][{'type': 'function', 'function': {'name': 'get_current_weather', 'description': '查看当前天气', 'parameters': {'type': 'object', 'properties': {'location': {'type': 'string', 'description': '城市和州,例如旧金山,加州'}, 'format': {'type': 'string', 'enum': ['摄氏度', '华氏度'], 'description': '使用的温度单位是'}}, 'required': ['location', 'format']}}}][/AVAILABLE_TOOLS][INST]今天巴黎的天气如何[/INST][TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '巴黎, 法国', 'format': '摄氏度'}}]</s>[TOOL_RESULTS][{'content': '22华氏度'}][/TOOL_RESULTS]\",
\"stream\": false,
\"raw\": true
}"
{
"response": "巴黎现在的温度是华氏22度(约零下5.6摄氏度)。(如果您更喜欢摄氏度,它大约是零下5.6度。)"
}
我们用 [INST][/INST]
标签包裹另一个用户的消息内容,以此来继续对话。
第三回合
curl --location "http://localhost:11434/api/generate" \
--header "Content-Type: application/json" \
--data "{
\"model\": \"mistral:7b-instruct-v0.3-fp16\",
\"prompt\": \"[AVAILABLE_TOOLS][{'type': 'function', 'function': {'name': 'get_current_weather', 'description': '获取当前天气', 'parameters': {'type': 'object', 'properties': {'location': {'type': 'string', 'description': '城市和州,例如:旧金山,CA'}, 'format': {'type': 'string', 'enum': ['celsius', 'fahrenheit'], 'description': '使用的温度单位。'}}, 'required': ['location', 'format']}}}][/AVAILABLE_TOOLS][INST]今天的巴黎天气如何?[/INST][TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '巴黎', 'format': 'celsius'}}]</s>[TOOL_RESULTS][{'content': '22华氏度'}][/TOOL_RESULTS] 当前巴黎的温度是华氏22度。如果更喜欢摄氏度,大约是零下5.6度。[INST]旧金山的天气呢?[/INST]\",
\"stream\": false,
\"raw\": true
}"
{
"response": "[TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '旧金山市, CA', 'format': '华氏'}}]"
}
我们在响应中又收到了一个函数调用。我们将执行该函数,然后将其输出添加到提示内容中,再次调用LLM模型。
第4回合:
curl --location "http://localhost:11434/api/generate" \
--header "Content-Type: application/json" \
--data "{
\"model\": \"mistral:7b-instruct-v0.3-fp16\",
\"prompt\": \"[AVAILABLE_TOOLS][{'type': 'function', 'function': {'name': 'get_current_weather', 'description': '获取当前天气', 'parameters': {'type': 'object', 'properties': {'location': {'type': 'string', 'description': '城市和州,例如:旧金山,加利福尼亚州'}, 'format': {'type': 'string', 'enum': ['摄氏度', '华氏度'], 'description': '使用的温度单位。'}}, 'required': ['location', 'format']}}}][/AVAILABLE_TOOLS][INST]今天巴黎的天气怎么样?[/INST][TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '巴黎, 法国', 'format': '摄氏度'}}]</s>[TOOL_RESULTS][{'content': '22华氏度 (约-5.6摄氏度)'}][/TOOL_RESULTS] 当前巴黎的气温是22华氏度(约-5.6摄氏度).[INST]旧金山呢?[/INST][TOOL_CALLS] [{'name': 'get_current_weather', 'arguments': {'location': '旧金山, 加利福尼亚州', 'format': '华氏度'}}][TOOL_RESULTS][{'content': '25摄氏度 (约77华氏度)'}][/TOOL_RESULTS]\",
\"stream\": false,
\"raw\": true
}"
{
"response": "旧金山现在的气温是25摄氏度。(如果你更喜欢华氏度,那大约是77华氏度。)"
}
Langchain.rb (Langchain的Ruby实现)
如果你正在使用 Ruby,并想尝试像 Mistral 这样的开源模型进行多轮对话和工具调用,可以试试 Langchain.rb 这个工具。
require "langchain"
# 通过 Ollama 实例化 LLM 客户端
llm = Langchain::LLM::Ollama.new(url: ENV["OLLAMA_URL"], default_options: { completion_model: "mistral:7b-instruct-v0.3-fp16"})
# 创建助手实例
assistant = Langchain::Assistant.new(
llm: llm,
工具: [Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])]
)
在这个例子中,我们使用了一个预构建的Langchain::Tool::Weather
工具。你可以轻松地创建自己的工具类和工具。这些工具将自动被包裹在[AVAILABLE_TOOLS][/AVAILABLE_TOOLS]
标签中。
开始和助手聊天吧:
assistant.add_message_and_run(content: "今天巴黎天气咋样?")
查看从大型语言模型收到的最后一条消息:
assistant.messages.last
# <Langchain::Messages::OllamaMessage:0x000000011e199bb8
# @content="[TOOL_CALLS] [{\"name\"=>\"weather__execute\", \"arguments\"=>{\"input\"=>\"Paris\"}}]",
# @角色="助手",
# @工具调用=[{"name"=>"weather__execute", "参数"=>{"输入"=>"Paris"}}]>
你可以手动输入工具调用的输出,或者再次运行程序,让工具自动运行。
# 手动输入
assistant.submit_tool_output(output: "[{content: '25C'}]") # 输入内容为 '25C'
// 注释:自动模式
assistant.run(auto_tool_execution: true) // 注释:自动工具执行
auto_tool_execution:
选项将会自动执行任何待执行的工具调用,并追加带有输出的新消息。
更多关于如何使用助手的信息请参阅这里: https://github.com/patterns-ai-core/langchainrb?tab=readme-ov-file#creating-an-assistant(点击链接可查看)。
1- 这篇博客文章中的很多想法都来自于Rick Lamers在2024年6月的AI工程师世界博览会上关于“开源LLM中的工具使用”的演讲。
- 最近合并的Ollama PR 表明,即将推出的新版本将支持函数调用(接受
tools:
参数)。 - Groq 刚刚发布了迄今为止性能最佳的开源函数调用模型:https://wow.groq.com/introducing-llama-3-groq-tool-use-models/
- 如果你想更深入地了解这个主题,HuggingFace提供了一个很好的资源:https://huggingface.co/docs/transformers/main/chat_templating#advanced-tool-use--function-calling
- @dottxtai 提供的基准测试表明,结构化生成有助于提升模型在BFCL上的表现:https://x.com/dottxtai/status/1797692104023363765 和 https://x.com/dottxtai/status/1798443290913853770.
- 这些研究表明,在简单的函数调用(不涉及复杂的计划和链式函数调用)方面,对开源模型进行精细化调整可能与专有模型一样有效。(https://gorilla.cs.berkeley.edu/blogs/8_berkeley_function_calling_leaderboard.html)
共同学习,写下你的评论
评论加载中...
作者其他优质文章