当前位置:   article > 正文

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

开源llm 支持function calling吗

前言

> 推荐电脑阅读

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

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

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

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

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

  1. [{
  2. "role": "user",
  3. "content": "What's the weather like in San Francisco, Tokyo, and Paris?",
  4. tools = [
  5. {
  6. "type": "function",
  7. "function": {
  8. "name": "get_current_weather",
  9. "description": "Get the current weather in a given location",
  10. "parameters": {
  11. "type": "object",
  12. "properties": {
  13. "location": {
  14. "type": "string",
  15. "description": "The city and state, e.g. San Francisco, CA",
  16. },
  17. "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
  18. },
  19. "required": ["location"],
  20. },
  21. },
  22. }
  23. ]
  24. }]

相比第一个请求,我们加了一个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:

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


部署 QWen 72B

假设你的模型地址为:

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

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

  1. import ray
  2. ray.init(address="auto",namespace="default")
  3. llm = ByzerLLM()
  4. model_location="/home/byzerllm/models/Qwen-72B-Chat"
  5. max_model_len = 24000
  6. llm.setup_gpus_per_worker(8).setup_num_workers(1).setup_infer_backend(InferBackend.VLLM)
  7. llm.deploy(
  8. model_path=model_location,
  9. pretrained_model_type="custom/auto",
  10. udf_name=chat_model_name,
  11. infer_params={"backend.max_num_batched_tokens":24000,
  12. "backend.max_model_len":max_model_len}
  13. )
  14. llm.setup_default_model_name("chat")
  15. llm.setup_max_model_length("chat",max_model_len)
  16. llm.setup_template("chat",Templates.qwen())

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

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

输出内容大概像这样:

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

准备一些函数

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

  1. from typing import List,Dict,Any,Annotated
  2. import pydantic
  3. import datetime
  4. from dateutil.relativedelta import relativedelta
  5. def compute_date_range(count:Annotated[int,"时间跨度,数值类型"],
  6. unit:Annotated[str,"时间单位,字符串类型",{"enum":["day","week","month","year"]}])->List[str]:
  7. '''
  8. 计算日期范围
  9. Args:
  10. count: 时间跨度,数值类型
  11. unit: 时间单位,字符串类型,可选值为 day,week,month,year
  12. '''
  13. now = datetime.datetime.now()
  14. now_str = now.strftime("%Y-%m-%d %H:%M:%S")
  15. if unit == "day":
  16. return [(now - relativedelta(days=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
  17. elif unit == "week":
  18. return [(now - relativedelta(weeks=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
  19. elif unit == "month":
  20. return [(now - relativedelta(months=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
  21. elif unit == "year":
  22. return [(now - relativedelta(years=count)).strftime("%Y-%m-%d %H:%M:%S"),now_str]
  23. return ["",""]
  24. def compute_now()->str:
  25. '''
  26. 计算当前时间
  27. '''
  28. return datetime.datetime.now().strftime("%Y-%m-%d %H:%M

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

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

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

具体用法如下:

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

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

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

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

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

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

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

接着看看:

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

还有这个:

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

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

我们原先的写法:

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

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

  1. def compute_date_range(count:Annotated[int,
  2. "时间跨度,数值类型,如果用户说的是几天,几月啥的,比较模糊,务必使用默认值"]=3,
  3. unit:Annotated[str,"时间单位,字符串类型",
  4. {"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. 对参数指定了强类型,有描述,以及有默认值,如果是枚举值还指定了可选枚举值。

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

Respond with Class

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

先定义一个类:

  1. import pydantic
  2. class Story(pydantic.BaseModel):
  3. '''
  4. 故事
  5. '''
  6. title: str = pydantic.Field(description="故事的标题")
  7. body: str = pydantic.Field(description="故事主体")

然后调用大模型:

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

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

  1. Story(
  2. title='勇敢的小兔子',
  3. 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) ,并在允许的情况下,尽量使用更大的模型,从而保证减少工程量。

赠送架构图一份:

ce45e97453a6d0f264910bb7c6343a5d.jpeg

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/200090
推荐阅读
相关标签
  

闽ICP备14008679号