ChatGLM3 prompt格式简介
ChatGLM3已经发布一段时间了,相较于ChatGLM2,它的一个重大更新是支持了工具和代码的调用,我们只需要将工具的输入输出定义完整,ChatGLM3会选择最可能满足用户诉求的工具,调用它,并依据返回的结果生成更好地答复。
根据官方描述,为了统一多项任务上的prompt格式以及防止prompt注入,ChatGLM3的prompt分为两部分,第一部分为system_prompt
,用于描述当前任务下ChatGLM3的身份以及特点,第二部分为对话中不同角色role
的发言,
<|system|>
You are ChatGLM3, a large language model trained by Zhipu.AI. Follow the user's instructions carefully. Respond using markdown.
<|user|>
Hello
<|assistant|>
Hello, I'm ChatGLM3. What can I assist you today?
在不同任务下,role
的取值范围不同:
- 多轮对话:
<|user|>
,<|assistant|>
- 工具调用和代码执行:
<|user|>
,<|observation|>
,<|assistant|>
其中的observation
为工具调用的结果,或者代码的执行结果。对于工具调用,在用户提出query后,ChatGLM3会首先以assistant
身份输出选择的工具和参数,然后需要将工具的调用结果以<|observation|>
身份输入模型,最终模型再以<|assistant|>
身份输出最终结果。对于代码调用,模型会以<|assistant|>
的身份并携带单词"interpreter"输出代码,然后需要将执行结果以<|observation|>
身份输入模型,之后可能发生若干轮“输出代码 -> 输入结果”的循环,最终模型会以<|assistant|>
身份但是不携带单词"interpreter"输出最终的答案。
官方代码改写
ChatGLM3的项目中携带了一个简单实现的工具调用示例,即composite_demo/demo_tool.py
文件,而可以访问的工具定义则在composite_demo/tool_registry.py
中实现。通过阅读代码和文档,我们知道,它是通过装饰函数register_tool
获取函数的钩子、注释、以及参数说明,因此,我们只需要参照其他工具的定义完成以下工作:
- 开发一个新工具
- 在函数头下添加注释
- 在工具上方添加装饰函数
register_tool
- 对工具的每个参数通过
Annotated
函数增加参数类型、参数含义、是否必须三个信息
为了演示,设计了一个获取明天天气的函数,具体实现是通过调用丫丫天气的api,具体参数含义请参见它们的api介绍,然后完成以上4步,最终的代码如下:
_CITY_ID_DICT = []
def get_city_id(city_name):
def _dfs(d, _city_name):
if 'list' in d:
for item in d['list']:
city_id = _dfs(item, _city_name)
if city_id is not None:
return city_id
else:
if d['en'] == _city_name or d['name'] == _city_name:
return d['city_id']
else:
return None
global _CITY_ID_DICT
if len(_CITY_ID_DICT) == 0:
with open('../composite_demo/city_id.json', 'r', encoding='utf8') as fp:
_CITY_ID_DICT = json.load(fp)
city_id = _dfs(_CITY_ID_DICT, city_name)
if city_id is None:
raise ValueError('您提供的城市名不在列表中')
return city_id
@register_tool
def get_tomorrow_weather(
city_name: Annotated[str, 'The name of the city to be queried', True],
) -> str:
"""
Get the weather for `city_name` of tomorrow
"""
city_id = get_city_id(city_name)
print(f'http://api.yytianqi.com/forecast7d?city={city_id}&key=e6g6eq1blkiqq01i')
import requests
try:
resp = requests.get(f'http://api.yytianqi.com/forecast7d?city={city_id}&key=你的api key')
resp.raise_for_status()
resp = resp.json()
print(resp)
data = resp['data']['list'][1]
ret = ''
# "tq1": "多云", //白天天气
ret += f'白天天气:{data["tq1"]}\n'
# "tq2": "晴", //夜间天气,当与白天天气相同时,两者可合并为一个
if 'tq2' not in data:
ret += f'夜间天气:{data["tq1"]}\n'
else:
ret += f'夜间天气:{data["tq2"]}\n'
# "numtq1": "01", //白天天气编码
# "numtq2": "00", //夜间天气编码
# "qw1": "6", //白天气温
ret += f'白天气温:{data["qw1"]}℃\n'
# "qw2": "-5", //夜间气温
ret += f'夜间气温:{data["qw2"]}℃\n'
# "fl1": "3-4级", //白天风力
ret += f'白天风力:{data["fl1"]}\n'
# "numfl1": "1", //白天风力编码
# "fl2": "微风", //夜间风力
ret += f'夜间风力:{data["fl2"]}\n'
# "numfl2": "0", //夜间风力编码
# "fx1": "北风", //白天风向
ret += f'白天风向:{data["fx1"]}\n'
# "numfx1": "8", //白天风向编码
# "fx2": "无持续风向", //夜间风向,如白天风力风向与夜间风力风向一致,可合并为一行
if 'fx2' not in data:
ret += f'夜间风向:{data["fx1"]}\n'
else:
ret += f'夜间风向:{data["fx2"]}\n'
# "numfx2": "0", //夜间风向编码
# "date": "2016-03-09" //预报日期
except:
import traceback
ret = "Error encountered while fetching weather data!\n" + traceback.format_exc()
return ret
其中,city_id.json
可以从丫丫天气城市ID编码列表,直接复制下来保存到本地即可,同时还需要在官网注册将你的api key替换到代码中去。
效果
在未增加工具之前,我们如果询问模型“北京明天的天气”,由于系统中只有一个官方提供的查询当前天气的工具,因此模型只能选择该工具,并返回当前的时间,这与用户的实际诉求是不一致的。而我们新增工具后,理论上,模型应该会选择新工具并提供明天的天气情况的简介。实际运行与此预估相符。