工具调用是大模型重要的能力之一,能够抑制大模型胡说八道,增加模型输出真实性可靠性。有些大模型在接口上不支持工具调用,但其实任何大模型本质上都是词语接龙,我们可以把工具调用指令拼接到提示词里面来实现。
添加必要的导入:
py
import re
import os
import json
import openai
首先我们需要这么一个提示词模板,它描述了可用工具列表,工具调用的返回和回传格式,还有示例。示例算是小样本提示,在没有特别微调的模型上,能够引导模型理解和输出工具调用。其中可用工具列表是个占位符,之后填充。
py
TOOLCALL_PMT = '''
## 工具调用指南
### 可用工具
在回答任何问题时,你可以调用一次或多次如下工具:
```
{tool_def}
```
### 返回格式
在决定调用工具时,请按照如下格式返回工具调用,确保内容包含在"[tool]...[/tool]"中,任何其它内容将会被忽略。如果不决定调用工具,不要输出任何"[tool]...[/tool]"内容。
[tool]
[{"id": "uuid", "tool": "tool name", "parameters": {"parameter name": "parameter value"}}]
[/tool]
用户调用工具后,将结果以如下格式传回:
[tool-result]
[{"id": "uuid", "result": "result"}]
[/tool-result]
### 示例
这是一个可用工具列表的示例:
```
{"tools": [{"name": "plus_one", "description": "Add one to a number", "parameters": {"type": "object","properties": {"number": {"type": "string","description": "The number that needs to be changed, for example: 1","default": "1",}},"required": ["number"]}},{"name": "minus_one", "description": "Minus one to a number", "parameters": {"type": "object","properties": {"number": {"type": "string","description": "The number that needs to be changed, for example: 1","default": "1",}},"required": ["number"]}}]}
```
如果你想计算`42 + 1`,可以返回:
[tool]
[{"id": "c3d16bba-9216-449e-8d46-d389fbca6cb5", "tool": "plus_one", "parameters": {"number": 42}}]
[/tool]
用户计算后,传回结果:
[tool-result]
[{"id": "c3d16bba-9216-449e-8d46-d389fbca6cb5", "result": 43}]
[/tool-result]
请注意,上述只是个示例,并不代表`plus_one`和`plus_minus`真实存在。
'''
定义工具字典,这里提供了两个工具,模拟查询航班号和航班价格:
py
def get_flight_number(date: str, departure: str, destination: str):
flight_number = {
"北京": {
"上海": "1234",
"广州": "8321",
},
"上海": {
"北京": "1233",
"广州": "8123",
}
}
return flight_number.get(departure, {}).get(destination, "0")
def get_ticket_price(date: str, flight_number: str):
return "1000"
tool_dict = {
"get_flight_number": get_flight_number,
"get_ticket_price": get_ticket_price
}
工具信息按照 OpenAI 的格式写就可以了:
tool_defs = [
{
"type": "function",
"function": {
"name": "get_flight_number",
"description": "根据始发地、目的地和日期,查询对应日期的航班号",
"parameters": {
"type": "object",
"properties": {
"departure": {
"description": "出发地",
"type": "string"
},
"destination": {
"description": "目的地",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": ["departure", "destination", "date"]
},
}
},
{
"type": "function",
"function": {
"name": "get_ticket_price",
"description": "查询某航班在某日的票价",
"parameters": {
"type": "object",
"properties": {
"flight_number": {
"description": "航班号",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": ["flight_number", "date"]
},
}
},
]
定义一个函数来调用大模型并处理工具调用。
- 首先拼接提示词,并作为系统提示词插入到消息列表最上面
- 然后调用一次大模型
- 用正则匹配返回文本里是否带有
[tool]...[/tool],-
如果是,用 JSON 库解析,找到合适的工具并调用
-
然后把工具调用的结果回传,再次调用大模型,重复步骤 2~3
-
否则直接返回文本
def call_llm_with_toolcall(
msgs, model_name,
tool_defs, tool_dict,
):
if isinstance(msgs, str):
msgs = [{'role': 'user', 'content': msgs}]
tool_defs_str = json.dumps(tool_defs)
toolcall_pmt = TOOLCALL_PMT.replace('{tool_def}', tool_defs_str)
msgs = [{
'role': 'system',
'content': toolcall_pmt
}] + msgs
client = openai.OpenAI(
base_url=openai.base_url,
api_key=openai.api_key,
)
print(f'msg: {msgs[-1]["content"]}')
res = client.chat.completions.create(
messages=msgs,
model=model_name,
).choices[0].message.content.strip()
print(f'res: {res}')TOOLCALL_RE = r'\[tool\]([\s\S]+)\[/tool\]' m = re.search(TOOLCALL_RE, res) while m: toolcalls = json.loads(m.group(1)) toolcall_res_list = [] for tc in toolcalls: tc_res = tool_dict[tc['tool']](**tc['parameters']) toolcall_res_list.append({'id': tc['id'], 'result': tc_res}) toolcall_res_str = json.dumps(toolcall_res_list) msgs += [{ 'role': 'assistant', 'content': res }, { 'role': 'user', 'content': f'[tool-result]{toolcall_res_str}[/tool-result]' }] print(f'msg: {msgs[-1]["content"]}') res = client.chat.completions.create( messages=msgs, model=model_name, ).choices[0].message.content.strip() print(f'res: {res}') m = re.search(TOOLCALL_RE, res) return res
-
测试代码:
py
openai.api_key = os.environ.get('OPENAI_API_KEY')
openai.base_url = os.environ.get('OPENAI_BASE_URL')
model_name = os.environ.get('OPENAI_MODEL')
msgs = [{
'role': 'system',
'content': '你是一个...'
}, {
'role': 'user',
'content': '帮我查询2024年1月20日,从北京出发前往上海的航班号和价格',
}]
ans = call_llm_with_toolcall(msgs, model_name, tool_defs, tool_dict)
print(ans)
我们可以看到如下输出:
ques: 帮我查询2024年1月20日,从北京出发前往上海的航班号和价格
res: [tool]
[{"id": "query_123", "tool": "get_flight_number", "parameters": {"departure": "北京", "destination": "上海", "date": "2024-01-20"}}]
[/tool]
ques: [tool-result][{"id": "query_123", "result": "1234"}][/tool-result]
res: [tool]
[{"id": "query_456", "tool": "get_ticket_price", "parameters": {"flight_number": "1234", "date": "2024-01-20"}}]
[/tool]
ques: [tool-result][{"id": "query_456", "result": "1000"}][/tool-result]
res: 2024年1月20日从北京出发前往上海的航班号为 1234,票价为 1000 元。
2024年1月20日从北京出发前往上海的航班号为 1234,票价为 1000 元。
可以看到模型首先决定调用get_flight_number工具,获取航班号之后,又调用get_ticket_price工具,顺利完成查询。