给开源大模型带来Function Calling、 Respond With Class

简介: OpenAI 在他的多个版本的模型里提供了一个非常有用的功能叫 Function Calling,就是你传递一些方法的信息给到大模型,大模型根据用户的提问选择合适的方法,然后输出给你,你再来执行。

前言

> 推荐电脑阅读

OpenAI 在他的多个版本的模型里提供了一个非常有用的功能叫 Function Calling,就是你传递一些方法的信息给到大模型,大模型根据用户的提问选择合适的方法,然后输出给你,你再来执行。

为了便于理解,我们从聊天开始,你发给模型一个问题,是使用类似下面的json格式:

[{
"role": "user", 
"content": "What's the weather like in San Francisco, Tokyo, and Paris?"
}]

模型会返回一个json格式数据给你。这里我们问的是当前的天气问题,显然大模型不可能知道(当然了,很多模型服务自己内置了一些包括检索等插件,这是另外一个话题,单纯的大语言模型是无法调用外部工具来获取外部信息的。),所以这里我们需要提供一个计算天气的函数给他。他会在你问的确实是天气的情况下,选择这个计算函数,并且将问题里的信息拆解成函数参数提供给你,这样你可以根据他给到的信息直接执行函数。

我们来看看加了“函数” 加持的样子

[{
"role": "user", 
"content": "What's the weather like in San Francisco, Tokyo, and Paris?",
tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        }
    ]
}]

相比第一个请求,我们加了一个tools字段,里面详细的描述了函数的名称,用法,参数列表,参数含义等等。相应的大模型会在他的返回字段里,多一个tool_calls 字段。你从这里可以拿到被选中的函数的具体信息,包括从问题抽取出来的参数值。接着你根据这些信息具体的调用你的函数,从而完成最后的天气调用。

所以实际上 function calling 的作用很简单,根据问题,根据你提供的函数列表,选择合适的一个或者多个函数给到你,你自己来执行。

那如果我想在开源大模型里用这个功能,怎么办呢?这不,Byzer-LLM 提供了一个比 OpenAI 更加易用的 Function Calling能力, 而且还额外送了个新的礼包:Respeond With Class, 就是真的把大模型当做一个方法调用,返回值要符合函数返回值的定义。来,我们一一来解读。

下载一个开源模型和安装 Byzer-LLM

首先,我们必须有一个开源的模型,这里我们从 ModelScope 下载 QWen-70B 大模型。选择 ModelScope 是因为 HuggingFace 下载速度太慢, 选择 QWen-70B 是因为 Function Calling 是比较吃大模型能力的,较为复杂的函数调用,还是需要大一点的模型。

从 ModelScope 下载模型:

git clone https://modelscope.cn/qwen/Qwen-72B-Chat.git

接着我们使用 [Byzer-LLM](https://github.com/allwefantasy/byzer-llm) 部署下模型。Byzer-LLM 一个基于Ray的,支持大模型预训练,微调和部署的 分布式Python 库。

在你的有显卡的机器(推荐 Ubuntu22 / 8卡 3090或4090)上安装下 Byzer-LLM:

git clone https://github.com/allwefantasy/byzer-llm
cd byzer-llm
pip install -r requirements.txt
pip install -U vllm
pip install -U byzerllm
ray start --head

部署 QWen 72B

假设你的模型地址为:

/home/byzerllm/models/Qwen-72B-Chat

那么你可以直接复制黏贴下面的代码,使用 python 运行即可。

import ray
ray.init(address="auto",namespace="default") 
llm = ByzerLLM()
model_location="/home/byzerllm/models/Qwen-72B-Chat"
max_model_len = 24000
llm.setup_gpus_per_worker(8).setup_num_workers(1).setup_infer_backend(InferBackend.VLLM)
llm.deploy(
    model_path=model_location,
    pretrained_model_type="custom/auto",
    udf_name=chat_model_name,
    infer_params={"backend.max_num_batched_tokens":24000,
                  "backend.max_model_len":max_model_len}
)
llm.setup_default_model_name("chat")
llm.setup_max_model_length("chat",max_model_len)
llm.setup_template("chat",Templates.qwen())

运行完成后,你可以简单测试下是不是可以聊天了:

t = llm.chat_oai([{
    "content":'''你好,你是谁''',
    "role":"user"    
}])
t[0].output

输出内容大概像这样:

我是通义千问,由阿里云开发的人工智能助手。我被设计用来回答各种问题、提供信息和与用户进行对话。有什么我可以帮助你的吗?
说明部署没问题。

准备一些函数

比如,用户经常会问,"最近xxx" 之类的问题,我们需要提取出精确的时间后改写query方便后续应用处理,这种情况,我们可以定义两个函数:

from typing import List,Dict,Any,Annotated
import pydantic 
import datetime
from dateutil.relativedelta import relativedelta
def compute_date_range(count:Annotated[int,"时间跨度,数值类型"],
                       unit:Annotated[str,"时间单位,字符串类型",{"enum":["day","week","month","year"]}])->List[str]:
    '''
    计算日期范围
    Args:
        count: 时间跨度,数值类型
        unit: 时间单位,字符串类型,可选值为 day,week,month,year
    '''        
    now = datetime.datetime.now()
    now_str = now.strftime("%Y-%m-%d %H:%M:%S")
    if unit == "day":
        return [(now - relativedelta(days=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
    elif unit == "week":
        return [(now - relativedelta(weeks=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
    elif unit == "month":
        return [(now - relativedelta(months=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
    elif unit == "year":
        return [(now - relativedelta(years=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
    return ["",""]
def compute_now()->str:
    '''
    计算当前时间
    '''
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M

第一个函数其实就是计算时间区间,第二个函数当用户问现在几点啦啥的,可以调用这个函数回答用户。

激动时刻来临了:将这些函数作为工具给到大模型

Byzer-LLM 将 Python 作为 LLM 的第一语言,你可以直接把函数传递给大模型,而不需要像 OpenAI 那样,写一堆的 json 描述文件。

具体用法如下:

t = llm.chat_oai([{
    "content":'''计算当前时间''',
    "role":"user"    
}],tools=[compute_date_range,compute_now],execute_tool=True)
t[0].values
## output: ['2023-12-18 17:30:49']

在上面的代码中,我们增加了两个参数:

1. tools:    一个函数列表
2. execute_tool: 是否直接执行选中的函数,而不是返回函数信息

我们让大模型计算下当前的时间,所以大模型选择了 compute_now 函数,然后因为我们配置了 execute_toolTrue, 所以他直接运行了 compute_now 函数,你可以通过 t[0].values 拿到 compute_now 的返回值。

惊不惊喜,意不意外,使用竟然这么简单,这么 Python 化。

现在再让我们问两个问题:

t = llm.chat_oai([{
    "content":'''你吃饭了么?''',
    "role":"user"    
}],tools=[compute_date_range,compute_now],execute_tool=True)
if t[0].values:
    print(t[0].values[0])
else:
    print(t[0].response.output)   
## output: '您好,我是一个人工智能语言模型,暂时无法吃饭。'
我们问了一个和时间无关的问题,大模型很好的没有调用任何工具,单纯的返回说他没办法吃饭。

接着看看:

t = llm.chat_oai([{
    "content":'''最近三个月趋势''',
    "role":"user"    
}],tools=[compute_date_range,compute_now],execute_tool=True)
t[0].values
## output: [['2023-09-18 17:31:21', '2023-12-18 17:31:21']]
很精准。

还有这个:

t = llm.chat_oai([{
    "content":'''最近几天的趋势''',
    "role":"user"    
}],tools=[compute_date_range,compute_now],execute_tool=True)
t[0].values

不按常理出牌啊,竟然说的最近几天,那到底是几天阿呀喂。这个时候可能不同的模型会反应不一样,有的会无法调用函数,有的会自己给一个默认值,比如Qwen 给的默认值是 7。那我们怎么控制这种模糊的问发呢?直接修改我们的函数签名:

我们原先的写法:

def compute_date_range(count:Annotated[int,"时间跨度,数值类型"],
                       unit:Annotated[str,"时间单位,字符串类型",{"enum":["day","week","month","year"]}])->List[str]:

可以看到, count/unit 我们都没给默认值。我们直接给个默认值就行了:

def compute_date_range(count:Annotated[int,
                                       "时间跨度,数值类型,如果用户说的是几天,几月啥的,比较模糊,务必使用默认值"]=3,
                       unit:Annotated[str,"时间单位,字符串类型",
                                      {"enum":["day","week","month","year"]}]="day")->List[str]:

这里我们设置为默认值 count 为 3, 当然,光给一个默认值 3 还不够,因为大模型其实没那么聪明,你需要在参数解释里稍微说明下:如果用户说的是几天,几月啥的,比较模糊,务必使用默认值。这样就好了。

再执行,会返回三天的时间区域给你:

[['2023-12-18 13:00:07', '2023-12-21 13:00:07']]

这里我们提出一个新概念。

LLM友好函数

下面这段代码就是 LLM 友好函数,因为他包含了几个要素:

  1. 函数命名,参数命名规范
  2. 对函数有介绍和描述
  3. 对参数指定了强类型,有描述,以及有默认值,如果是枚举值还指定了可选枚举值。
def compute_date_range(count:Annotated[int,                                       "时间跨度,数值类型,如果用户说的是几天,几月啥的,比较模糊,务必使用默认值"]=3,
                       unit:Annotated[str,"时间单位,字符串类型",
                                      {"enum":["day","week","month","year"]}]="day")->List[str]:
    '''
    计算日期范围
    '''

Respond with Class

这个属于额外复送的一个功能。我们看一眼代码,大家就知道有啥用了,

先定义一个类:

import pydantic 
class Story(pydantic.BaseModel):
    '''
    故事
    '''
    title: str = pydantic.Field(description="故事的标题")
    body: str = pydantic.Field(description="故事主体")

然后调用大模型:

t = llm.chat_oai([
{
    "content":f'''请给我讲个故事,分成两个部分,一个标题,一个故事主体''',
    "role":"user"
},
],response_class=Story)
t[0].value

我们额外加了一个参数,response_class=Story, 现在大模型不再返回一段文本,而是一个 Story 对象:

Story(
title='勇敢的小兔子', 
body='在一个美丽的森林里,住着一只可爱的小兔子。小兔子非常勇敢,有一天,森林里的动物们都被大灰狼吓坏了。只有小兔子站出来,用智慧和勇气打败了大灰狼,保护了所有的动物。从此,小兔子成为了森林里的英雄。')

酷不酷。

总结

这里我们用 ModelScope + Qwen72B + Byzer-LLM 演示了在开源模型使用OpenAI 类似的 Function Calling 功能,并且提供了额外的 Respond with Class 功能。我们把 Python 作为大模型第一语言,并且提出了 LLM 友好函数的概念。

此外,如果用户希望构建基于大模型的业务应用,我们推荐存储采用 [Byzer-Retrieval](https://github.com/allwefantasy/byzer-retrieval), 应用开发框架使用 [Byzer-Agent](https://github.com/allwefantasy/byzer-agent) ,并在允许的情况下,尽量使用更大的模型,从而保证减少工程量。

赠送架构图一份:

image.png

相关文章
|
9天前
|
SQL 人工智能 JSON
【AI大模型应用开发】2.2 Function Calling连接外部世界 - 【实战】查询数据库
【AI大模型应用开发】2.2 Function Calling连接外部世界 - 【实战】查询数据库
14 0
|
9天前
|
人工智能 JSON 自然语言处理
【AI大模型应用开发】2.1 Function Calling连接外部世界 - 入门与实战(1)
【AI大模型应用开发】2.1 Function Calling连接外部世界 - 入门与实战(1)
27 0
|
1天前
|
存储
function(函数)
在 Lua 中,函数作为第一类值可存储于变量,如示例所示:`factorial1` 和 `factorial2` 存储相同函数。此外,函数可作为参数传递,如 `testFun` 接收一个表和一个匿名函数,该匿名函数在迭代中处理键值对,输出 `key1=val1` 和 `key2=val2`。
|
5天前
|
Serverless 应用服务中间件 数据安全/隐私保护
Serverless 应用引擎操作报错合集之在阿里函数计算中,函数执行超时,报错Function time out after如何解决
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
15 4
|
1月前
|
资源调度 Serverless 计算机视觉
高斯函数 Gaussian Function
**高斯函数,或称正态分布,以数学家高斯命名,具有钟形曲线特征。关键参数包括期望值μ(决定分布中心)和标准差σ(影响分布的宽度)。当μ=0且σ²=1时,分布为标准正态分布。高斯函数广泛应用于统计学、信号处理和图像处理,如高斯滤波器用于图像模糊。其概率密度函数为e^(-x²/2σ²),积分结果为误差函数。在编程中,高斯函数常用于创建二维权重矩阵进行图像的加权平均,实现模糊效果。
18 1
|
2月前
|
算法 Serverless C语言
CMake函数和宏(function和macro):使用函数和宏提高代码可读性
CMake函数和宏(function和macro):使用函数和宏提高代码可读性
33 1
|
2月前
|
存储 安全 编译器
【C++ 包装器类 std::function 和 函数适配器 std::bind】 C++11 全面的std::function和std::bind的入门使用教程
【C++ 包装器类 std::function 和 函数适配器 std::bind】 C++11 全面的std::function和std::bind的入门使用教程
33 0
|
2月前
|
SQL Oracle 关系型数据库
Flink的表值函数(Table-Valued Function,TVF)是一种返回值是一张表的函数
【2月更文挑战第17天】Flink的表值函数(Table-Valued Function,TVF)是一种返回值是一张表的函数
21 1
|
7月前
|
存储 数据安全/隐私保护
均匀散列函数(Uniform Hash Function)
均匀散列函数(Uniform Hash Function)是一种将不同长度的输入数据映射到相同大小的输出数据的散列函数。均匀散列函数的主要特点是,对于相同的输入数据,无论其长度如何,都会得到相同的输出散列值。这种散列函数常用于数据结构的存储和查找,例如哈希表、散列表等。
102 3
|
4月前
|
存储 SQL 安全
函数(Function)和存储过程(Stored Procedure)的区别(小白情感版)
函数(Function)和存储过程(Stored Procedure)的区别(小白情感版)
42 0

热门文章

最新文章