【Python大语言模型系列】谈一谈大模型的函数调用(Function Call)功能(案例+源码)

这是我的第411篇原创文章。

一、引言

ChatGLM 的函数调用功能可以增强模型推理效果或进行其他外部操作,包括信息检索、数据库操作、知识图谱搜索与推理、操作系统、触发外部操作等工具调用场景。本教程将介绍如何使用 ChatGLM 的函数调用功能,实现对模型与外部函数库的连接。

tools 是内容生成 API 中的可选参数,用于向模型提供函数定义。通过此参数,模型能够生成符合用户所提供规范的函数参数。请注意,API 实际上不会执行任何函数调用,仅返回调用函数所需要的参数。开发者可以利用模型输出的参数在应用中执行函数调用。

本教程包括以下3个部分:

  1. 如何使用 Chat Completion 接口向模型描述外部函数。

  2. 如何与模型交互,触发模型对函数的调用。

  3. 如何使用模型生成的结果调用外部函数。

二、实现过程

搭建一个用于演示的学生成绩数据库,该数据库仅包含一个数据表: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、机器学习、深度学习、人工智能系列基础知识与应用案例。致力于只做原创,以最简单的方式理解和学习,关注我一起交流成长。需要数据集和源码的小伙伴可以关注底部公众号添加作者微信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据杂坛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值