这是我的第411篇原创文章。
一、引言
ChatGLM 的函数调用功能可以增强模型推理效果或进行其他外部操作,包括信息检索、数据库操作、知识图谱搜索与推理、操作系统、触发外部操作等工具调用场景。本教程将介绍如何使用 ChatGLM 的函数调用功能,实现对模型与外部函数库的连接。
tools 是内容生成 API 中的可选参数,用于向模型提供函数定义。通过此参数,模型能够生成符合用户所提供规范的函数参数。请注意,API 实际上不会执行任何函数调用,仅返回调用函数所需要的参数。开发者可以利用模型输出的参数在应用中执行函数调用。
本教程包括以下3个部分:
-
如何使用 Chat Completion 接口向模型描述外部函数。
-
如何与模型交互,触发模型对函数的调用。
-
如何使用模型生成的结果调用外部函数。
二、实现过程
搭建一个用于演示的学生成绩数据库,该数据库仅包含一个数据表:100名学生的语数英、物化生成绩,基于该数据库实现几个查询和数据筛选函数,用于在LLM中进行调用。通过LLM的函数调用,非技术人员也能使用自然语言与数据库进行交互而不用写SQL代码,不需要了解任何技术上的东西,一切都在函数中进行实现;另外,对于技术人员,需要了解LLM的函数调用原理,才能有助于更好地实现相关函数。
2.1 创建示例数据库
以一个学生考试成绩问答服务为例,随机生成100名学生的语数英物化生成绩信息:
将DataFrame保存为sqlite数据库:
conn = sqlite3.connect('database.db')
df.to_sql('grades', conn, if_exists='replace', index=False)
cursor = conn.cursor()
# 查询数据表
cursor.execute('''SELECT name FROM sqlite_master WHERE type='table' ''')
print(cursor.fetchall())
# 查询数据表的字段
cursor.execute('''SELECT * from sqlite_schema''')
print(cursor.fetchall())
结果:
[('grades',)]
[('table', 'grades', 'grades', 2, 'CREATE TABLE "grades" (\n"姓名" TEXT,\n "语文" INTEGER,\n "数学" INTEGER,\n "英语" INTEGER,\n "物理" INTEGER,\n "化学" INTEGER,\n "生物" INTEGER,\n "总分" INTEGER\n)')]
2.2 实现数据库查询函数
实现一些简单的数据库查询操作,比如姓名查询、科目成绩查询等等,验证该数据库没有问题:
def search_grades_by_name(name):
''' 根据姓名精确查询该学生所有科目的成绩以及总分 '''
resp = cursor.execute(f''' SELECT * from grades WHERE 姓名='{name}' ''')
res = []
for i in resp.fetchall():
print(i)
_name, chinese, math, en, phy, che, bio, total = i
res.append(dict(姓名=_name, 语文=chinese, 数学=math, 英语=en, 物理=phy, 化学=che, 生物=bio, 总分=total))
return res
def search_grades_by_lastname(lastname):
''' 根据姓氏查询该姓氏学生的所有科目成绩以及总分 '''
resp = cursor.execute(f''' SELECT * from grades WHERE 姓名 LIKE '{lastname}%' ''')
res = []
for i in resp.fetchall():
_name, chinese, math, en, phy, che, bio, total = i
res.append(dict(姓名=_name, 语文=chinese, 数学=math, 英语=en, 物理=phy, 化学=che, 生物=bio, 总分=total))
return res
def search_by_name_subject(name, subject):
''' 根据学生姓名和科目查询某科目成绩或者总分 '''
resp1 = search_grades_by_name(name)
res = []
for i in resp1:
res.append({'姓名': i['姓名'], f'{subject}': i[subject]})
return res
def filter_by_subject_score(subject, score):
''' 根据某科目分数大于某阈值筛选学生姓名和该科目对应分数 '''
resp = cursor.execute(f''' SELECT * from grades WHERE '{subject}' >= '{score}' ''')
res = []
for i in resp.fetchall():
_name, chinese, math, en, phy, che, bio, total = i
res.append(dict(姓名=_name, 语文=chinese, 数学=math, 英语=en, 物理=phy, 化学=che, 生物=bio, 总分=total))
return res
def filter_by_total_score_top_n(subject, top_n=5):
''' 根据某科目或者总成绩排名,并获取前N名学生的成绩信息 '''
resp = cursor.execute(f''' SELECT * from grades ORDER BY {subject} DESC LIMIT {top_n} ''')
res = []
for i in resp.fetchall():
_name, chinese, math, en, phy, che, bio, total = i
res.append(dict(姓名=_name, 语文=chinese, 数学=math, 英语=en, 物理=phy, 化学=che, 生物=bio, 总分=total))
return res
# 测试这些函数调用没有问题
print(search_grades_by_name('张天宇'))
print(search_grades_by_lastname('高'))
print(search_by_name_subject('张天宇', '英语'))
print(filter_by_subject_score('语文', 120))
print(filter_by_total_score_top_n('总分', 5))
结果:
2.3 定义LLM中的函数调用格式
上面定义了五个函数,下面将这五个函数构造成供LLM进行选择的格式,需要包含每个函数的名称、描述、参数列表和参数的描述等等。
tools = [
{
"type": "function",
"function":{"name": "search_grades_by_name",
"description": "根据姓名精确查询该学生所有科目的成绩以及总分",
"parameters":{"type": 'object',
"properties":{ "name": { "type": "string", "description": "学生姓名" }},
"required": ["name"]
},
}
},
{
"type": "function",
"function":{"name": "search_grades_by_lastname",
"description": "根据姓氏查询该姓氏学生的所有科目成绩以及总分",
"parameters":{"type": 'object',
"properties":{"lastname": {"type": "string", "description": "学生的姓氏" } },
"required": ["lastname"]
},
}
},
{
"type": "function",
"function":{ "name": "search_by_name_subject",
"description": "根据学生姓名和科目查询某科目成绩或者总分",
"parameters":{"type": 'object',
"properties":{ "name": {"type": "string","description": "学生姓名"},
"subject":{ "type": "string", "description": "科目或者总分"}},
"required": ["name", "subject"]
},
}
},
{
"type": "function",
"function":{"name": "filter_by_subject_score",
"description": "根据某科目分数大于某阈值筛选学生姓名和该科目对应分数",
"parameters":{"type": 'object',
"properties":{"score": { "type": "int", "description": "成绩分数" },
"subject":{ "type": "string", "description": "科目或者总分"} },
"required": ["score", "subject"]
},
}
},
{
"type": "function",
"function":{ "name": "filter_by_total_score_top_n",
"description": "根据学生姓名和科目查询某科目成绩或者总分",
"parameters":{ "type": 'object',
"properties":{ "top_n": { "type": "int", "description": "排名前几"},
"subject":{ "type": "string", "description": "科目或者总分" } },
"required": ["subject"]
},
}
},
]
print(len(tools))
2.4 LLM中函数的调用
代码:
import json
from zhipuai import ZhipuAI
api_key = 'your_api_key'
client = ZhipuAI(api_key=api_key)
system_prompt = """你是一位专业的 AI 助手,你的任务是回答用户问题,可以利用工具接口获得用户想要的答案"""
def call_glm(messages, model="glm-4-plus", temperature=0.95, tools=None, top_p=0.7):
response = client.chat.completions.create(model=model,
messages=messages,
temperature=temperature,
top_p=top_p,
tools=tools)
return response
query = "刘晓楠的语文成绩?"
messages = [ {"role": "system", "content": system_prompt},
{"role": "user", "content": query} ]
try:
response = call_glm(messages, tools=tools)
messages.append(response.choices[0].message.model_dump())
except Exception as e:
print(e)
tools_call = response.choices[0].message.tool_calls[0]
print(tools_call.function)
tool_name = tools_call.function.name
args = tools_call.function.arguments
print(tool_name, args)
结果:
调用函数,获取函数返回结果,然后再使用LLM进行问答
fc_res = json.dumps(eval(tool_name)(**json.loads(args))) # function calling result
messages.append({"role": "tool", "content": f"{fc_res}", "tool_id": tools_call.id})
try:
response = call_glm(messages, tools=tools)
messages.append(response.choices[0].message.model_dump())
except Exception as e:
print(e)
print(messages[-1])
结果:
三、小结
总结一下LLM函数调用的工作原理:
1. 首先是提示词和函数定义:用户向LLM发出文本提示,向外部数据执行访问操作(比如:张三的语文成绩是多少?);提供可用的函数列表,该列表有特定的定义格式,主要说明该函数的名称、参数和该函数能够完成的功能,下面的实操部分会详细描述。
2. 函数检测和选择:LLM处理用户的提示,确定是否需要调用外部工具,如果需要它会从提供的函数列表中识别正确的函数,返回所选函数的名称和所需要的输入参数。
3. 执行:LLM只会返回函数名称和参数,不会执行函数,需要显式调用执行函数(有可能是多个,则需要按顺序执行),返回执行结果。
4. 生成最终的回答:通过第三步函数执行得到的结果,LLM将这些结果整合到响应中,生成更加准确、更符合用户意图的回答(类似于RAG)。
作者简介:
读研期间发表6篇SCI数据挖掘相关论文,现在某研究院从事数据算法相关科研工作,结合自身科研实践经历不定期分享关于Python、机器学习、深度学习、人工智能系列基础知识与应用案例。致力于只做原创,以最简单的方式理解和学习,关注我一起交流成长。需要数据集和源码的小伙伴可以关注底部公众号添加作者微信。